home *** CD-ROM | disk | FTP | other *** search
/ Aminet 52 / Aminet 52 (2002)(GTI - Schatztruhe)[!][Dec 2002].iso / Aminet / docs / mags / saku11.lha / Teksti / Järjestelmä.txt < prev    next >
Text File  |  1995-02-28  |  90KB  |  1,971 lines

  1. 5
  2. 1*
  3.  
  4. {3                  Järjestelmäohjelmoinnin alkeiskurssi - Osa 2
  5. {3                  --------------------------------------------
  6.  
  7. {3                         System Executive - ohjelmointi
  8. {3                         ------------------------------
  9.  
  10.                                   Sami Klemola
  11.  
  12.  
  13. Kurssin ensimmäisessä osassa tutustuttiin tietokoneen toimintaan  yleisellä  ta-
  14. solla sekä alustavasti Execin tehtäviin. Tässä ainakin toistaiseksi  viimeisessä
  15. osassa selviää, miten ohjelmia tehdään Amiga-ympäristöön. Artikkelissa  sivutaan
  16. myös konekieliohjelmointia, mutta pääpaino on C-kielessä. Tässä  osassa  käydään
  17. myös läpi Execin osat yksi kerrallaan, ja jokaisesta tulee esimerkkikoodia.  En-
  18. siksi kuitenkin katsotaan ohjelmoinnin vaiheita. 
  19.  
  20.  
  21. {3Ohjelmoinnin työkalut ja kääntäminen
  22. {3------------------------------------
  23.  
  24. Tarvitset kääntäjän ja linkkerin. Jos ohjelmoit konekielellä, tarvitset makroas-
  25. semblerin. Jos kirjoitat C-koodia, tarvitset  lisäksi  C-kääntäjän,  mutta  myös
  26. edelleen makroassemblerin, koska C-kääntäjä ei tuota valmista koodia,  vaan  AS-
  27. CII-muotoista assemblyä eli konekielen lähdekoodia. C-kääntäjän mukana voi tulla
  28. erityisesti sen tuottamaa koodia kääntämään tarkoitettu makroassembleri.  Sitten
  29. käännetään vielä makroassemblerilla  eli  konekielikääntäjällä,  aivan  kuin  C-
  30. kääntäjän tuottama tiedosto olisi itse kirjoittamaasi konekielilähdekoodia. Mak-
  31. roassembleri ei sekään tuota vielä ajettavaa ohjelmaa, vaan  objektikooditiedos-
  32. ton. Tämän jälkeen tulee vielä yksi vaihe, linkkaus. Linkkeri tulee  usein  mak-
  33. roassemblerin mukana, mutta niitä on saatavilla erillisinäkin.
  34.  
  35. Linkkeri yhdistää makroassemblerin  tuottaman  objektikoodin  sekä  tarvittaessa
  36. linkkerikirjastosta mukaan rutiineja, joita se kutsuu. Linkkeri hakee myös  kir-
  37. jastokutsujen osoituksien arvot koodiin ja C-koodin tapauksessa  liittää  mukaan
  38. tarvittavan startup-koodin, joka mm. tulkitsee komentorivin valmiiksi ja alustaa
  39. I/O:n. Kaikki nämä ajetaan usein yhdellä komennolla, joka voi ajaa skriptin  tai
  40. frontend-ohjelman. Esimerkki tällaisesta tulee  myöhemmin.  Ohjelmaan  voi  myös
  41. kuulua useita lähdekoodeja, joista jokainen käännetään  omaksi  objektikooditie-
  42. dostokseen. Linkkeri yhdistää silloin nämäkin tiedostot ja asettaa  niistä  toi-
  43. siinsa tehdyt osoitukset eli cross referencet, ristiviittaukset.
  44.  
  45. Muuta tarpeellista
  46.  
  47. Includet ovat olennainen osa ohjelmointia. Ne ovat tiedostoja,  jotka  määritte-
  48. levät käyttöjärjestelmän sekä muiden ohjelmien käyttämiä datarakenteita ym.  Au-
  49. todocit  ovat  kuvauksia  esimerkiksi  käyttöjärjestelmän   kirjastofunktioista.
  50. Niistä käy ilmi kullekin funktiolle annettavat parametrit, mitä funktio tekee ja
  51. mitä se palauttaa. Myös rekisterit, joihin parametrit sijoitetaan, näkyvät auto-
  52. doceissa. Tieto siitä hyödyttää erityisesti  konekieliohjelmoijaa.  Palautusarvo
  53. tulee aina D0:ssa. Jotkin funktiot voivat palauttaa kaksikin arvoa, jolloin toi-
  54. nen palautetaan yleensä  D1:ssä,  ja  sen  saa  myös  DOS:n  kautta  kyselemällä
  55. jälkikäteen.
  56.  
  57. Lisäksi tarvitaan amiga.lib, joka on nk. linker library. Tähän asti  on  puhuttu
  58. vain ajonaikaisista kirjastoista, jotka ohjelma avaa  kutsuakseen  niiden  funk-
  59. tioita. Ajonaikaiset kirjastot sijaitsevat LIBS:-hakemistossa. Linker library on
  60. aivan erilainen kirjasto. Ohjelman käyttämät rutiinit, jotka sijaitsevat kirjas-
  61. tossa, linkataan ohjelmatiedostoon mukaan, joten kirjastoa ei  tarvita  ohjelmaa
  62. ajettaessa. Tällaisten funktioiden käyttäminen tietysti pidentää  ohjelmaa  huo-
  63. mattavasti. Siksi onkin aina syytä käyttää vastaavaa ajonaikaisen kirjaston  ru-
  64. tiinia, jos sellainen vain on olemassa.
  65.  
  66. Lisäksi amiga.lib sisältää ajonaikaisten kirjastojen offset-arvot. Näitä  arvoja
  67. tarvitaan kutsuttaessa kirjaston funktioita. Offset lisätään  kirjaston  kantao-
  68. soitteeseen, jolloin saadaan hyppyosoite funktioon. Osoite on tosin vain  hyppy-
  69. taulukkoon, jossa on yleensä suoraan hyppy  varsinaiseen  koodiin.  Offset-arvot
  70. ovat negatiivisia, koska hyppytaulukko sijaitsee ja kasvaa kirjaston kantaosoit-
  71. teesta alaspäin. Kirjastofunktioiden käyttämisestä tulee  esimerkkejä  ja  lisää
  72. tietoa myöhemmin.
  73.  
  74. Ohjelman kääntäminen
  75.  
  76. Kuten on jo mainittu, ohjelma käännetään yleensä frontendilla. Itselläni on oma-
  77. tekoinen cc-ohjelma, joka ajaa edelleen dcc:n, joka on varsinainen DICE:n  fron-
  78. tend. Käytän cc:tä yksinkertaistamaan kääntämistä  entisestään.  Kirjoitan  vain
  79. "cc ohjelma", jolloin source-hakemistossa oleva lähdekoodi ohjelma.c  käännetään
  80. ja linkitetään valmiiksi ajettavaksi ohjelmaksi Progs-hakemistoon. Konekielipuo-
  81. lella käytössäni on niin ikään itse kirjoittamani Compile-ohjelma, joka ajaa en-
  82. sin makroassemblerin, joka kääntää source-hakemistossa (luonnollisesti pidän  C-
  83. ja konekielikoodit eri hakemistoissa, nämä ovat c/source ja  asm/source)  olevan
  84. lähdekoodin object-hakemistoon objektikoodiksi, ja sen jälkeen  linkkerin,  joka
  85. edelleen linkkaa tämän objektikoodin  ajettavaksi  ohjelmaksi.  Sekä  konekieli-
  86. että C-ohjelman tapauksessa linkataan mukaan amiga.lib:ssä määriteltyjä  arvoja,
  87. joihin tehdään ristiviittaus ohjelmassa. Lisäksi mukaan linkataan  mahdollisesti
  88. muuta objektikoodia sekä rutiineja omista kirjastoistani.
  89.  
  90. Käännöstyön ohjelmat
  91.  
  92. Olen jo maininnutkin DICE:n. Se on Matthew Dillonin C-ympäristö,  joka  on  eräs
  93. tämän hetken varteenotettavimmista Amigalla. Toinen suosittu paketti on gcc  eli
  94. GNU C, mutta se on massiivinen niin olemukseltaan (kaikkiaan yli kahdeksan mega-
  95. tavua vieläpä pakattuna) kuin muistinkulutukseltaankin (4-8 megatavua). SAS  C:n
  96. eli entisen Latticen tuki on lopetettu. Myös DICE on kaupallinen,  mutta  se  ei
  97. ole ollenkaan niin hinnakas kuin SAS C oli. DICE maksaa $150  normaalille  ihmi-
  98. selle ja $90 opiskelijalle. GNU C:n hyvä puoli on se, että se on ilmainen!  DICE
  99. ja gcc ovat kattavia paketteja, ja niiden mukana tulee kaikki  tarpeellinen  oh-
  100. jelmisto ja kaikkea ylimääräistä vielä lisäksi. Myös makroassembleri ja linkkeri
  101. kuuluvat kauppaan.
  102.  
  103. Konekieltä ohjelmoidessa kiinnitetään tietysti  suurempi  huomio  makroassemble-
  104. riin. Aikansa hyvin palvelleet MC ja a68k on jo syytä unohtaa.  Markkinoilla  on
  105. monia hyviä moderneja kääntäjiä, jotka  osaavat  myös  uusimpien  prosessoreiden
  106. osoitusmuodot ja erikoisuudet. Itse olen varsin mieltynyt suomalaiseen SNMA:aan,
  107. joka on erittäin nopea ja tehokas. Assemblerin kanssa  joutuu  myös  itse  kiin-
  108. nittämään huomiota linkkeriin. Vanhat alink ja blink ovat historiaa.  Itse  olen
  109. käyttänyt varsin menestyksekkäästi DICE:n mukana tulevaa dlink:iä myös omien ko-
  110. nekieliohjelmieni kääntämiseen. Ainakaan SNMA:n tuottaman  koodin  kanssa  dlin-
  111. killä ei ole ollut mitään ongelmia, kun taas blink kaatuili aika  ajoin,  samoin
  112. kuin MC. Dlink on hoidellut esimerkillisesti myös omat kirjastoni.
  113.  
  114. Kääntäminen käytännössä
  115.  
  116. C-kielinen ohjelma kääntyy näin:
  117.  
  118. dcc source/prg.c -o Progs/prg -2.0
  119.  
  120. Välivaiheet eli konekielinen lähdekoodi ja objektikoodi kirjoitetaan tilapäisha-
  121. kemistoon, joka on normaalisti RAM-asemalla sijaitseva T:. Optio  "-2.0"  määrää
  122. kääntäjän käyttämään käyttöjärjestelmäversion 2.0 includeja.
  123.  
  124. Konekielinen ohjelma prg kääntyy seuraavasti:
  125.  
  126. SNMA source/prg.asm OBJ /object/prg.o INCLUDE /include
  127. dlink object/mystartup.o object/prg.o lib/amiga.lib -o Progs/prg
  128.  
  129. SNMA haluaa välttämättä asettaa current diriksi sen hakemiston, jossa lähdekoodi
  130. on, joten  object-  ja  include-hakemistoihin  on  viitattava  taaksepäin.  SNMA
  131. käyttää AmigaDOS-tyylistä komentorivitekniikkaa, kun taas dlink  soveltaa  Unix-
  132. tyylisiä "-"-merkillä alkavia yksimerkkisiä "avainsanoja". 
  133.  
  134.  
  135. {3Ohjelmointi käytännössä
  136. {3----------------------- 
  137.  
  138. Tässä luvussa käsittelen yleisesti ohjelmointia Amigalla. Otan puheeksi joitakin
  139. asioita, jotka jokaisen ohjelmoijan tulee tietää.
  140.  
  141. Ohjelman rakenne, C-ohjelma
  142.  
  143. C-ohjelman pituus on heti ainakin viisi kilotavua. Se johtuu siitä, että  ohjel-
  144. malle tehdään kaikkea pientä valmiiksi. C-ohjelma saa  valmiina  parsetun  argu-
  145. menttijonon ja alustetut I/O-streamit stdin, stdout ja stderr. Alustuskoodi myös
  146. avaa kirjastoja valmiiksi. Ainakin DOS avataan aina, mutta esimerkiksi DICE osaa
  147. avata  myös  monet  muut  kirjastot  riippuen  siitä,  mitä  niistä   ohjelmassa
  148. käytetään! Usein kuitenkin on parasta avata ne itse ohjelmassa, koska esimerkik-
  149. si koodi voidaan kääntää toisella kääntäjällä, sellaisella, joka ei  automaatti-
  150. sesti avaa kirjastoja, jolloin tuloksena olisi ohjelma, joka ei avaa käyttämiään
  151. kirjastoja!
  152.  
  153. Lisäksi startup-koodi alustaa DATA- ja BSS-alueet. Tämä tulee  lähinnä  kysymyk-
  154. seen residentattavan ohjelman kanssa, koska dos.libraryn LoadSeg() huolehtii  jo
  155. niiden alustuksesta ladattaessa ohjelma DOS-asemalta. Residentattu ohjelma lada-
  156. taan muistiin kiinteästi, ja kaikki prosessit, jotka  sitä  ajavat,  suorittavat
  157. samaa koodia muistissa. Tällöin data-alueet joudutaan alustamaan itse  ohjelmas-
  158. sa, mutta C-kääntäjät osaavat ottaa sen  huomioon.  C-kääntäjät  tarjoavat  myös
  159. muita käteviä toimintoja, kuten pinon seurannan. Jos se on  päällä,  kun  vapaan
  160. pinon määrä laskee liian pieneksi, ohjelmaan sisällytetty koodi varaa lisää  pi-
  161. nomuistia!
  162.  
  163. Ohjelman rakenne, konekieliohjelma
  164.  
  165. Konekieliohjelmalle ei tehdä mitään valmiiksi, vaan se ajetaan aivan suoraan DO-
  166. Sista. Konekieliohjelma saa osoittimen argumenttijonoon A0:ssa ja  sen  pituuden
  167. D0:ssa.  Useimmiten  sen  joutuu  vieläpä  itse  päättämään   komennolla   clr.b
  168. -1(a0,d0.l). Jos ohjelma haluaa käyttää jotakin muuta kirjastoa kuin Execiä,  on
  169. se avattava. Jos se haluaa tulostaa tai lukea tietoa esimerkiksi näppäimistöltä,
  170. on filehandle haettava itse.
  171.  
  172. Konekieliohjelman tekeminen onkin huomattavasti hankalampaa kuin C-kielisen. Sen
  173. lisäksi, että konekielellä ohjelmointi  on  hitaampaa  sen  yksityiskohtaisemman
  174. luonteen takia, on tehtävä enemmän työtä. Tietysti on mahdollista kirjoittaa ge-
  175. neerinen  oma  startup-koodi  käytettäväksi  konekieliohjelman  kanssa,  jolloin
  176. työmäärä pienenee ratkaisevasti. Konekieli tulee  kysymykseen  erittäin  pienien
  177. ohjelmien kanssa sekä C-kielisen ohjelman  funktioiden  kirjoituskielenä.  Usein
  178. C-kieliset ohjelmat sisältävät konekielisiä osia. Jotkin osat on kätevää ja tar-
  179. koituksenmukaista kirjoittaa konekielellä  C:n  sijaan.  Kaikkea  ei  C:llä  voi
  180. tehdäkään. Esimerkiksi keskeytykset edellyttävät  konekielen  käyttöä,  ja  myös
  181. kirjastot on parasta kirjoittaa konekielellä.
  182.  
  183. Yleistä ohjelmista
  184.  
  185. Ohjelma saa käyttää kaikkia prosessorin rekisterejä, mutta pinoa  ja  pino-osoi-
  186. tinta ei saa sotkea. Ohjelman tulee palauttaa validi arvo (katso  alla).  Alioh-
  187. jelmat saavat käyttää rekisterejä D0/D1/A0/A1. Tämä pätee myös kirjastofunktioi-
  188. hin sekä  C-ohjelman  omiin  konekielialiohjelmiin.  Käyttäjä  odottaa  saavansa
  189. tietää ohjelmasta sen versionumeron. Tiedon saa Version-komennolla, mutta  vain,
  190. jos se on sisällytetty ohjelmaan. Versiotieto annetaan erityisellä  merkkijonol-
  191. la, joka alkaa "$VER:".
  192.  
  193. C-kielellä:
  194.  
  195. unsigned char versionstring[] = "$VER: Ohjelma 1.00";
  196.  
  197. Konekielellä: 
  198.  
  199.          dc.b     "$VER: Ohjelma 1.00",0
  200.  
  201. Nyt Version-komento osaa löytää versiotiedon ja tulostaa  "Ohjelma  1.00".  Ver-
  202. siostringin tulisi olla juuri tässä formaatissa eikä  siinä  saisi  olla  mitään
  203. ylimääräistä. Jotkut ohjelmoijat laittavat siihen itsekeskeisyydessään omia  ni-
  204. miään, copyrighteja ja muuta siihen kuulumatonta roskaa, mikä voi olla harmitta-
  205. vaa.
  206.  
  207. Lisäksi käyttäjä odottaa voivansa breikata ohjelman <CTRL>-C:llä. C-kääntäjä voi
  208. tarjota automaattisen breikkisysteeminkin, mutta mielestäni se  kannattaa  tehdä
  209. itse: 
  210.  
  211.          if(SetSignal(0,0) & SIGBREAKF_CTRL_C) error("user break");
  212.  
  213. Tämän kun laittaa sellaiseen paikkaan, että se suoritetaan tarpeeksi usein,  voi
  214. käyttäjä keskeyttää halutessaan ohjelman suorituksen.
  215.  
  216. Esimerkkejä konekielellä
  217.  
  218. Tässä on lyhyt konekieliohjelma: 
  219.  
  220.          moveq    #18,d0
  221.          addq.l   #2,d0
  222.          rts
  223.  
  224. Ohjelma ei tee muuta kuin laskee yhteen 18 ja 2 ja palauttaa arvon. Palautusarvo
  225. 20 vastaa FAIL-tilaa, joten ajettaessa tämä ohjelma saadaan ilmoitus  sen  "fai-
  226. laamisesta" eli jokin olisi mennyt vakavasti pieleen, jos kyseessä  olisi  ollut
  227. oikea ohjelma.  Ohjelman  tulee  onnistuessaan  palauttaa  arvo  0.  Tässä  ovat
  228. tärkeimmät arvot: 
  229.  
  230.          0        OK       Kaikki meni kuten suunniteltua
  231.          5        WARN     Varoitus, jotain pientä meni vikaan
  232.          10       ERROR    Virhe, kaikki ei onnistunut
  233.          20       FAIL     Epäonnistuminen, kaikki meni päin seiniä
  234.  
  235. Tässä on vähän järkevämpi ohjelma, mutta vain vähän: 
  236.  
  237.          include  "exec/types.i"             ; yleisiä määrittelyjä
  238.          include  "exec/libraries.i"         ; kirjastojen määrittelyjä
  239.  
  240.          SECTION  prg_code,CODE
  241.  
  242.          movea.l  4,a6                       ; Haetaan Execin kantaosoite
  243.          movea.l  a0,a2                      ; Argumenttijonon osoitin
  244.          move.l   d0,d3                      ; ja pituus talteen
  245.          lea      Dos(pc),a1                 ; Osoitin dos.libraryn nimeen
  246.          moveq    #LIBRARY_VERSION,d0        ; kirjastoversio, mikä tahansa
  247.          Lib      OpenLibrary                ; versio kelpaa meille tässä
  248.          tst.l    d0                         ; testataan, saatiinko
  249.          bne      DosOK                      ; kirjasto auki
  250.          moveq    #20,d0                     ; failataan, jos ei saatu
  251.          rts
  252. DosOK    movea.l  d0,a6                      ; DOSBase a6:een
  253.          Lib      Output                     ; Haetaan "stdout"
  254.          move.l   d0,d1                      ; annetaan sen fh Writelle
  255.          move.l   a2,d2                      ; argumenttijonon osoitin
  256.          Lib      Write                      ; pituus on valmiiksi d3:ssa
  257.          movea.l  a6,a1                      ; DOSBase a1:een, josta
  258.          movea.l  4,a6                       ; kirjasto
  259.          Lib      CloseLibrary               ; suljetaan
  260.          clr.l    d0                         ; ja poistutaan, kaikki OK
  261.          rts
  262.  
  263.          SECTION  prg_data,DATA
  264.  
  265. Dos      dc.b     "dos.library",0
  266.  
  267.          END
  268.  
  269. Ohjelma tulostaa sille annetun argumenttijonon,  mutta  ilman  LF-koodia,  joten
  270. prompti tulostuu sen perään. Ohjelma kaivannee  selvennystä.  A6  on  rekisteri,
  271. jossa tulee olla aina kulloinkin kutsuttavan kirjaston kantaosoite,  koska  sitä
  272. kutsutaan sen kautta. Lisäksi kirjaston rutiinit saattavat myös hyödyntää sitä -
  273. Execin tapauksessa harvemmin, mutta varsinkin Intuition käyttää sitä usein. Kir-
  274. jastot käsitellään yksityiskohtaisesti myöhemmin.
  275.  
  276. LIBRARY_VERSION on exec/libraries.h-includetiedostossa määritelty kiinteä  muut-
  277. tuja,  jonka  arvo  tällä  hetkellä  on  33.  Se  tarkoittaa  vanhinta   tuettua
  278. käyttöjärjestelmäversiota, ja suositus on avata kirjasto sillä,  ellei  tarvitse
  279. uudemman kirjaston ominaisuuksia. Kaikki  merkkijonot  tulee  päättää  nollalla.
  280. Output palauttaa filehandlen (fh) tulostuskanavaan, joka on normaalisti Shellin,
  281. josta ohjelma käynnistettiin, ikkuna. Se annetaan Writelle, kuin  myös  ohjelman
  282. parametreinä saamat argumenttijonon osoitin ja pituus.
  283.  
  284. Tässä tapauksessa merkkijonoa ei tarvitse päättää nollaan, koska Write  kirjoit-
  285. taa tietyn määrän merkkejä sen sijaan, että se tulostaisi  niitä  nollaan  asti,
  286. kuten esimerkiksi C-kielen printf(). Pituus Writelle annetaan D3:ssa,  johon  se
  287. siirretään D0:sta jo heti ohjelman alussa. Tämä on yksi syy, miksi  konekielellä
  288. ohjelmointi on C:tä tehokkaampaa. Siinä on mahdollista tehdä tuollaisia ennakoi-
  289. via toimenpiteitä, jotka lyhentävät ohjelmaa. C-kääntäjän  tuottamassa  koodissa
  290. pituus olisi kirjoitettu ties minne ja  sitten  vasta  D3:een,  kun  sitä  olisi
  291. siellä tarvittu, mutta me osaamme laittaa sen sinne jo valmiiksi.
  292.  
  293. Ohjelmassa käytetty Lib on makro, joka kutsuu kirjastofunktiota: 
  294.  
  295. Lib MACRO * routinename[,basereg]
  296.       xref _LVO\1                   ; _LVO#? eli offset-arvo haetaan
  297.       ifnc '\2',''                  ; jostain muualta eli amiga.lib:stä
  298.       movea.l \2,a6                 ; jos basereg on määritelty, siirretään
  299.       endc                          ; base sieltä A6:een, kuten kuuluu
  300.       jsr _LVO\1(a6)                ; ja kutsutaan itse funktiota
  301.       ENDM
  302.  
  303. LVO eli Library Offset Vector on arvo, jonka mukaan funktiota kutsutaan.  Writen
  304. tapauksessa offsetin nimi on _LVOWrite. Se asetetaan xref:llä ulkoiseksi muuttu-
  305. jaksi. Linkkeri täyttää muuttujan arvon hakemalla sen amiga.lib:stä.  Ohjelmassa
  306. tarkistetaan OpenLibraryn palauttama arvo, joka  on  kirjaston  kantaosoite  tai
  307. nolla, jos kirjasto ei jostain syystä auennut, jolloin emme voi jatkaa ja ohjel-
  308. man tulee failata. Kaikki palautusarvot pitää aina tarkistaa. Jos  funktio  vain
  309. voi failata, on mahdollinen  virhekoodi  löydettävä,  koska  useimmiten  virheen
  310. jälkeen ei voida tehdä sitä, mitä pitäisi ja sen yrittäminen ehkä kaataa  koneen
  311. tai ainakin saa aikaan suuria vaikeuksia.
  312.  
  313. Esimerkkejä C-kielellä
  314.  
  315. Tässä tulee äskeistä konekieliohjelmaa pidemmälle viety C-kielinen ohjelma: 
  316.  
  317. #include "proto/exec_protos.h"
  318. #include "exec/types.h"
  319. #include "dos/dos.h"
  320.  
  321. main(ac,char*av[]);
  322.  
  323. main(ac,char*av[]) {
  324.     printf("Ensimmäinen parametri on \"%s\".\n",av[1]);
  325.     return(RETURN_OK);
  326. };
  327.  
  328. Ohjelma on huomattavasti yksinkertaisempi, vaikka se tulostaa jopa johdannon  ja
  329. rivinvaihdon sekä nimenomaisesti ensimmäisen parametrin. RETURN_OK vastaa  arvoa
  330. 0. Paluuarvojen määrittelyt ovat dos.h-tiedostossa. Itse asiassa edellisessä ko-
  331. nekieliohjelmassakin olisi pitänyt failauskohdassa olla RETURN_FAIL. Ensimmäinen
  332. include, "exec_protos.h", sisältää prototyypit kaikille Execin  funktioille.  Se
  333. on syytä aina ladata, samoin kuin muidenkin käytettävien  kirjastojen  prototyy-
  334. pit, jottei tulisi hankaluuksia.
  335.  
  336. Tässä tulee uusi versio: 
  337.  
  338. #include "proto/exec_protos.h"
  339. #include "proto/dos_protos.h"
  340. #include "exec/types.h"
  341. #include "dos/dos.h"
  342.  
  343. _main(void);
  344.  
  345. unsigned char *as;
  346.  
  347. _main(void) {
  348.     as=GetArgStr();
  349.     Write(Output(),as,strlen(as));
  350.     return(RETURN_OK);
  351. };
  352.  
  353. Nyt  käytän  _main()-entrypointia.  Normaalisti  _main():ksi  tulee  DICE:n  oma
  354. _main()-funktio, mutta nyt korvaan sen omallani. Varsinainen _main()  tekee  oh-
  355. jelman alustukset, joita olen jo muutamaan kertaan luetellut, minkä  jälkeen  se
  356. kutsuu main()-funktiota, joka on normaalisti ohjelman pääfunktio. Nyt se kuiten-
  357. kin on _main(). Hyöty on se, että tässä tapauksessa tarpeeton alustus  jää  pois
  358. ja ohjelman pituudeksi tuli  vain  616  tavua.  Tällöin  kuitenkin  printf()  on
  359. käyttökelvoton, samoin argc ja argv,  mutta  yllä  kuvatuilla  tavoilla  ne  voi
  360. kiertää. Tosin argumenttijonon parseamisen joutuu tekemään itse, mutta usein  se
  361. itse tehdäänkin. Tämä ei kuitenkaan ole suotava tapa toimia. DICE on hyvin riip-
  362. puvainen omasta alustuskoodistaan, joten sitä ei pidä mennä jättämään pois, jol-
  363. lei tiedä varmasti, mitä tekee.
  364.  
  365. Tähän loppuu artikkelin yleinen ohjelmointiosuus. Uusia asioita ohjelmien  teke-
  366. miseen liittyen voi tulla vielä esille, mutta nyt aletaan käydä läpi Execin osia
  367. yksi kerrallaan. Jokainen osa kuvataan  yksityiskohtaisesti  ja  esimerkkikoodia
  368. tulee, tästä lähtien kaikki C-kielellä, joka on *se* kieli, jolla ohjelmat tulee
  369. kirjoittaa, paitsi erityisesti konekielistä koodia  edellyttävissä  tilanteissa.
  370. Mukana on myös edistyneempää ohjelmointia. 
  371.  
  372.  
  373. {3Signaalit
  374. {3---------
  375.  
  376. Signaalit ovat Execin tarjoama keino välittää yksinkertaista tietoa  esimerkiksi
  377. tehtävien välillä. Signaalit ovat tehtävienvälisen kommunikaation kaikkein  alin
  378. taso, jonka varassa lepäävät kaikki muut järjestelmät.  Useat  signaalit  voivat
  379. olla aktiivisia yhtä aikaa. Jokaisella tehtävällä on 32  signaalibittiä,  joista
  380. 16 on varattu käyttöjärjestelmän käyttöön. Loput 16  ovat  vapaasti  ohjelmoijan
  381. käytettävissä. Harvemmin niitä kuitenkin tarvitsee suoraan itse  käyttää,  joten
  382. voit hypätä tämän kappaleen yli. Pääasia on,  että  tiedät,  mitä  signaalibitit
  383. ovat,  koska  et  todennäköisesti  tule  koskaan  tarvitsemaan   niiden   suoraa
  384. hyödyntämistä.
  385.  
  386. Viestiportin käyttöön voidaan signaalibitti varata  automaattisesti,  mutta  jos
  387. sitä tarvitaan johonkin muuhun tarkoitukseen, se tehdään näin: 
  388.  
  389. UBYTE signal;
  390.  
  391. if((signal = AllocSignal(-1)) < 0 )
  392.     printf("Ei vapaita signaalibittejä"); else {
  393.     printf("Varatun signaalibitin numero on %ld.\n",signal);
  394.     FreeSignal(signal);
  395. }
  396.  
  397. Signaalia odotetaan Wait()-funktiolla. Sille ei kuitenkaan anneta  signaalibitin
  398. numeroa vaan maski, joka muodostetaan kaikista  odotettavista  signaalibiteistä.
  399. Wait() odottaa, kunnes yksi signaaleista saadaan ja palauttaa maskin,  josta  on
  400. saatavissa selville aktivoitunut signaali. Usea signaali voi aktivoitua  samalla
  401. kertaa, joten kaikki odotetut signaalit tulee tutkia: 
  402.  
  403. firstsigmask = 1L << firstsigbit;
  404. secondsigmask = 1L << secondsigbit;
  405.  
  406. signalmask = Wait(firstsigmask | secondsignalmask | SIGBREAKF_CTRL_C);
  407.  
  408. if(signals & firstsigmask)
  409.     printf("Ensimmäinen signaali tuli aktiiviseksi\n");
  410. if(signals & secondsigmask)
  411.     printf("Toinen signaali tuli aktiiviseksi\n");
  412. if(signals & SIBREAKF_CTRL_C)
  413.     printf("User break\n");
  414.  
  415. Tässä odotamme kahta signaalia, joiden bittinumerot ovat muuttujissa firstsigbit
  416. ja secondsigbit. Niistä  muodostetaan  maskit,  jotka  yhdistetään  ja  annetaan
  417. Wait():lle, joka sitten aikanaan palauttaa maskin, jossa ovat päällä kaikki  ak-
  418. tiiviset signaalit. Tämän jälkeen ne  kaikki  tutkitaan  yksitellen.  Mukana  on
  419. vielä SIGBREAKF_CTRL_C, joka on yksi käyttöjärjestelmälle kuuluvista  kiinteistä
  420. signaalibiteistä.  Tehtävä  saa  tällaisen  signaalin,   kun   käyttäjä   painaa
  421. <CTRL>-C:tä.
  422.  
  423. Signaalibittejä voi itse manipuloida funktiolla SetSignal(). Sen käyttämiseen on
  424. kiinnitettävä erityistä huomiota, koska se suorittaa erittäin matalan tason toi-
  425. mintoja. SetSignal():lle annetaan kaksi parametriä,  signaalibittien  ja  maskin
  426. uudet arvot. Maski on luku, jossa ovat ne  signaaleja  vastaavat  bitit  päällä,
  427. joiden arvo asetetaan arvoluvun vastaavien bittien mukaisesti. Tämä ei ole  eri-
  428. tyisen yksinkertainen asia, joten tässä on muutama esimerkki: 
  429.  
  430. SetSignal(0,-1)   nollaa kaikki signaalit
  431. SetSignal(6,3)    nollaa nollabitin ja asettaa ykkösbitin
  432. SetSignal(0,0)    ei muuta mitään, vaan ainoastaan palauttaa signaalit
  433.  
  434. Näistä viimeistä voidaan käyttää signaalien tilan tarkistamiseen. Se  ei  nollaa
  435. mahdollista aktiivista signaalibittiä, kuten Wait(), joten se on  tehtävä  käsin
  436. signaalin saamisen jälkeen. Parhaiten SetSignal():n toimintaa  selventänee  kes-
  437. kimmäinen esimerkki. Arvosta 6 ei käytetä ollenkaan bittiä 2, arvoltaan 4, koska
  438. maskissa ovat päällä bitit 0 ja 1, joten vain ne asetetaan arvon mukaisesti. Ar-
  439. vossa bitti 0 on pois päältä ja bitti 1 päällä, joten myös  vastaavat  signaalit
  440. saavat nämä tilat. 
  441.  
  442. Exec tarjoaa viisi funktiota signaalien hyödyntämiseen:
  443.  
  444. ULONG SetSignal( unsigned long newSignals, unsigned long signalSet );
  445.  
  446. Asettaa ja/tai tarkistaa halutut signaalibitit. Tästä olikin jo esimerkki aikai-
  447. semmin <CTRL>-C:n tunnistamisessa. Tällä funktiolla voidaan tutkia tehtävän sig-
  448. naalien tilaa antamalla kummaksikin parametriksi nolla, mutta yleensä signaaleja
  449. tulisi odottaa Wait()-funktiolla. 
  450.  
  451. ULONG Wait( unsigned long signalSet );
  452.  
  453. Odottaa haluttuja signaalibittejä ja palauttaa aktiiviset  signaalit  ja  nollaa
  454. kaikki aktivoituneet signaalit. Tästä syystä on ehdottomasti tarkistettava kaik-
  455. ki palautetut bitit, jotta signaali ei pääsisi livahtamaan ohi huomaamatta, kos-
  456. ka seuraavalla kerralla se ei enää ole päällä. Wait(0) aiheuttaisi loppumattoman
  457. odottamisen. 
  458.  
  459. void Signal( struct Task *task, unsigned long signalSet );
  460.  
  461. Signaloi toista tehtävää. Signaalibitti  tulee  tällöin  signaloitavan  tehtävän
  462. signaaleista. Tällaista toimintatapaa ei voi hyödyntää tietämättä, mitä  signaa-
  463. lia toinen tehtävä odottaa. Kyseeseen tuleekin lähinnä ohjelman alatehtävä, joka
  464. on varannut signaalibitin ja kertonut päätehtävälle sen numeron, jotta  se  osaa
  465. aktivoida oikean signaalin. 
  466.  
  467. BYTE AllocSignal( long signalNum );
  468.  
  469. Varaa signaalin käyttöä varten. Numero voi olla -1,  jolloin  varataan  seuraava
  470. vapaana oleva. Signaalit eivät ole globaaleja, vaan jokaisella tehtävällä on oma
  471. joukkonsa signaaleita. 
  472.  
  473. void FreeSignal( long signalNum );
  474.  
  475. Vapauttaa varatun signaalin. 
  476.  
  477.  
  478. {3Listat ja jonot
  479. {3---------------
  480.  
  481. Amigan ympäristön olennainen ominaisuus on dynaamisuus.  Myös  järjestelmän  da-
  482. tastruktuurit ovat dynaamisia rakenteeltaan. Järjestelmä on joustava eikä rajoja
  483. juuri ole. Dataa tallennetaan dynaamisesti luotaviin struktuureihin (joukko tie-
  484. toa ennalta määrätyssä muodossa),  joita  pidetään  listoissa.  Lista  voi  olla
  485. tyhjä, mutta ei koskaan täysi. Lista voi myös  olla  järjestetty,  jolloin  sitä
  486. kutsutaan jonoksi (queue). Käytän siitä hämäännyksen  vähentämiseksi  vastaisuu-
  487. dessa englanninkielistä nimeä.
  488.  
  489. Exec pitää kaiken järjestelmään liittyvän tiedon listoissa. Lista koostuu heade-
  490. rista ja kaksoislinkatusta ketjusta elementtejä, joita kutsutaan nodeiksi.  Hea-
  491. der sisältää osoittimen listan ensimmäiseen ja viimeiseen nodeen. Header  toimii
  492. handlena koko listaan. Listoihin voidaan liittää nodeja ja niitä voidaan poistaa
  493. niistä. Listaa käsitellessä ei tarvitse tietää, millaista  tietoa  se  sisältää.
  494. Exec tarjoaa listojen käsittelyyn joukon funktioita, joita voidaan käyttää kaik-
  495. kien listojen kanssa.
  496.  
  497. Node on ryhmä toisiinsa liittyvää tietoa, joka kuvaa jonkin asian. Itse  asiassa
  498. nodet ovat erilaisia struktuureja, jotka alkavat Node-struktuurilla, jonka avul-
  499. la listaa ylläpidetään. Nodet voivat sijaita missä tahansa muistissa  toisistaan
  500. riippumatta. Ne pitävät kiinni toisistaan kahden osoittimen avulla.  Kaksoislin-
  501. kattu lista tarkoittaa sitä, että jokaisessa nodessa on osoitin sitä  edeltävään
  502. ja seuraavaan nodeen listassa. Näitä kutsutaan nimillä predecessor ja successor.
  503. Listan ensimmäisen noden eli Head-noden edeltäjä on listan  header.  Vastaavasti
  504. listan viimeisen noden eli Tail-noden jäljittäjä on niin  ikään  listan  header.
  505. Kuten sanottu, headerissa on osoittimet listan Head-  ja  Tail-nodeen.  Tyhjässä
  506. listassa nämä osoittavat toisiinsa. Listasta on olemassa myös supistettu versio,
  507. MinList. Nodejen määrittelyt tulevat tässä: 
  508.  
  509. struct Node {
  510.     struct  Node *ln_Succ;  /* Osoitin seuraavaan nodeen (successor) */
  511.     struct  Node *ln_Pred;  /* Osoitin edelliseen nodeen (predecessor) */
  512.     UBYTE   ln_Type;        /* Noden tyyppi, sama kuin listan tyyppi */
  513.     BYTE    ln_Pri;         /* Prioriteetti, listan järjestämistä varten */
  514.     char    *ln_Name;       /* ID-stringi, päättyy nollaan */
  515. };
  516.  
  517. struct MinNode {
  518.     struct MinNode *mln_Succ;
  519.     struct MinNode *mln_Pred;
  520. };
  521.  
  522. Tyyppejä on monia. Listassa voi olla vain keskenään samantyyppisiä nodeja. Lista
  523. voidaan järjestää nodejen prioriteettien mukaiseen järjestykseen.  Tällöin  sitä
  524. kutsutaan jonoksi eli queueksi. Nimeä harvemmin  käytetään.  Yleisimmät  nodejen
  525. tyypit ovat NT_TASK, NT_INTERRUPT, NT_MSGPORT ja NT_MESSAGE. Yksi esimerkki  ni-
  526. men hyödyntämisestä on kirjasto. Kirjastot alkavat nodella, jolloin nimi on kir-
  527. jaston nimi, esim. exec.library. Tällöin noden tyyppi on vastaavasti NT_LIBRARY.
  528. Exec pitää kirjastot kirjastolistassa, josta ne voidaan helposti  löytää.  Tässä
  529. tulevat headerien määrittelyt: 
  530.  
  531. struct List {
  532.    struct  Node *lh_Head;           /* Head-node */
  533.    struct  Node *lh_Tail;           /* Nolla */
  534.    struct  Node *lh_TailPred;       /* Tail-node */
  535.    UBYTE   lh_Type;
  536.    UBYTE   l_pad;
  537. };
  538.  
  539. struct MinList {
  540.    struct  MinNode *mlh_Head;
  541.    struct  MinNode *mlh_Tail;
  542.    struct  MinNode *mlh_TailPred;
  543. };
  544.   
  545. Minimaalinen lista käy yksiin täyden listan alun kanssa, mutta  sen  tyyppiä  ei
  546. voida testata. Tässä on kaavio, joka selventää headerin rakennetta: 
  547.  
  548.          Head-node         Tail-node         Headeri
  549.  
  550.          ln_Succ                             lh_Head
  551.  
  552.          ln_Pred = 0       ln_Succ = 0       lh_Tail = 0
  553.  
  554.                            ln_Pred           lh_TailPred
  555.  
  556. Header on siis tavallaan listan Head- ja Tail-nodejen yhteensulautuma, kun  nii-
  557. den ajatellaan menevän lomittain päällekkäin. Header alustetaan kuvaamaan tyhjää
  558. listaa asettamalla lh_Head osoittamaan lh_Tailiin ja lh_TailPred lh_Headiin sekä
  559. nollaamalla lh_Tail. Myös tyyppi tulee asettaa oikeaksi,  jos  käytetään  täyttä
  560. headeria. Amiga.lib sisältää rutiinin listan alustamiseen - C:llä  NewList()  ja
  561. konekielellä NEWLIST, joka on lists.i-tiedostossa  määritelty  makro.  Tässä  on
  562. kuitenkin vastaava koodi kummallakin kielellä: 
  563.  
  564. /* c */
  565.  
  566. struct List list;
  567.  
  568. list.lh_Head = (struct Node *) &list.lh_Tail;
  569. list.lh_Tail = 0;
  570. list.lh_TailPred = (struct Node *) &list.lh_Head;
  571.  
  572. ; assembly (a0 = osoitin alustettavaan headeriin)
  573.  
  574.          move.l   a0,LH_HEAD(a0)
  575.          addq.l   #4,LH_HEAD(a0)
  576.          clr.l    LH_TAIL(a0)
  577.          move.l   a0,LH_TAILPRED(a0)
  578.  
  579. Listan tyhjyyden voi tarkistaa. Siihen on monia tapoja, mutta tässä on eräs kum-
  580. mallakin kielellä: 
  581.  
  582. /* c */
  583.  
  584. if(list->lh_TailPred == (struct Node *)list) printf("Lista on tyhjä\n");
  585.  
  586. ; assembly
  587.  
  588.          cmp.l    LH_TAILPRED(a0),a0
  589.          beq      List_is_empty
  590.  
  591. Lista skannataan helposti ottamalla aina seuraavan noden osoitin:
  592.  
  593. struct List *list;
  594. struct Node *node;
  595.  
  596. for(node = list->lh_Head; node->ln_Succ; node = node->ln_Succ)
  597.     printf("Node nimeltä %s on osoitteessa %lx.\n",node->ln_Name,node);
  598.  
  599. Tässä ovat Execin listojen käsittelyyn tarjoamat funktiot:
  600.  
  601. void Insert( struct List *list, struct Node *node, struct Node *pred );
  602.  
  603. Lisää noden listaan haluttuun paikkaan. Tässä  list  on  tietysti  lista,  johon
  604. lisätään, ja node on node, joka lisätään. Lisäksi pred on osoitin nodeen,  jonka
  605. jälkeen  listassa  uusi  node  sijoitetaan.  Uuden  noden  predecessor  osoittaa
  606. lisäämisen jälkeen siihen ja successor siihen, joka  ennen  lisäämistä  oli  sen
  607. perässä listassa. Insert():llä voidaan lisätä node myös listan alkuun  tai  lop-
  608. puun, mutta se ei ole tehokasta. Siihen kannattaa käyttää alla  olevia  erityis-
  609. funktioita, ja Insert():iä vain silloin, kun node sijoitetaan tiettyyn  paikkaan
  610. keskelle listaa. 
  611.  
  612. void AddHead( struct List *list, struct Node *node );
  613.  
  614. Lisää noden listan alkuun eli siitä tulee sen Head-node. Lisäämisen jälkeen hea-
  615. derin lh_Head osoittaa tähän nodeen. 
  616.  
  617. void AddTail( struct List *list, struct Node *node );
  618.  
  619. Lisää noden listan loppuun eli siitä tulee  sen  Tail-node.  Lisäämisen  jälkeen
  620. headerin lh_TailPred osoittaa tähän nodeen. 
  621.  
  622. void Remove( struct Node *node );
  623.  
  624. Poistaa noden listasta. Poistamisen jälkeen noden successor ja predecessor  ovat
  625. invalideja. 
  626.  
  627. struct Node *RemHead( struct List *list );
  628.  
  629. Poistaa listan Head-noden.
  630.  
  631. struct Node *RemTail( struct List *list );
  632.  
  633. Poistaa listan Tail-noden.
  634.  
  635. void Enqueue( struct List *list, struct Node *node );
  636.  
  637. Lisää noden listaan kuten Insert(), mutta sen paikka listassa määräytyy  priori-
  638. teettien mukaan. Listan ensimmäinen node on suuriprioriteettisin, ja prioriteet-
  639. ti laskee loppua kohden. Samanprioriteettiset nodet laitetaan  listaan  FIFO-pe-
  640. riaatteella eli node lisätään viimeisen samanprioriteettisen noden perään.  Lis-
  641. ta, johon nodet lisätään Enqueue():lla on jono eli queue. 
  642.  
  643. struct Node *FindName( struct List *list, UBYTE *name );
  644.  
  645. Etsii listasta halutunnimisen noden ja palauttaa osoittimen ensimmäiseen nodeen,
  646. jonka nimi täsmää annetun merkkijonon kanssa. Erikoisuutena on se, että funktion
  647. avulla voidaan etsiä kaikki listassa olevat tietynnimiset  nodet,  vaikka  niitä
  648. olisi useita. Tällöin headerin sijaan funktiolle annetaankin sen palauttaman no-
  649. den osoitin, jolloin se jatkaa etsimistä eteenpäin listassa ja palauttaa mahdol-
  650. lisesti seuraavan täsmäävän noden: 
  651.  
  652. struct List *list;
  653. struct Node *node;
  654.  
  655. unsigned char name[];
  656.  
  657. if(node = FindName(list,name)) while(node) {
  658.     printf("Node nimeltä %s löytyi osoitteesta %lx.\n",node->ln_Name,node);
  659.     node = FindName((struct List *)node,name);
  660. } else printf("Nodea ei löytynyt nimellä %s.\n",name);
  661.  
  662.  
  663.  
  664. {3Portit ja viestit
  665. {3-----------------
  666.  
  667. Seuraava askel syvemmälle Execin toimintaan on "interprocess communication"  eli
  668. tehtävienvälinen kommunikaatio. Tästä olikin puhetta  jo  kurssin  ensimmäisessä
  669. osassa. Nyt katsotaan, miten tieto siirtyy ohjelmien välillä käytännössä.
  670.  
  671. Ideana on, että tehtävät lähettävät viestejä toisilleen. Viestit ovat samoja dy-
  672. naamisesti varattavia struktuureja, joista oli puhetta edellisessä luvussa. Myös
  673. keskeytys voi lähettää viestin tehtävälle tai päin vastoin. Viesti on kaksiosai-
  674. nen. Ensimmäinen osa on vakio kaikilla  viesteillä  ja  pitää  sisällään  tietoa
  675. viestistä, mm. sen koon. Edelleen, se alkaa nodella, joihin tutustuttiin edelli-
  676. sessä luvussa. Toinen osa on viestin sisältö, data, joka  siirretään.  Sitä  voi
  677. olla jopa melkein 64 kilotavua.
  678.  
  679. Viesti lähetetään aina kohdeporttiin. Siihen kuuluu viestilista, johon  saapuvat
  680. viestit niiden noden avulla lisätään. Kun viesti otetaan vastaan, se  poistetaan
  681. portin listasta. Myös porttistruktuuriin kuuluu node. Sen avulla Exec pitää por-
  682. tit listassa. Näin tietty portti voidaan helposti etsiä edellisessä luvussa  ku-
  683. vatuin keinoin.
  684.  
  685. Viestiä ei kopioida, vaan se sijaitsee staattisesti muistissa. Käytännössä  vain
  686. osoitin viestiin siirretään, mutta teoriassa katsotaan myös  käyttöoikeus  vies-
  687. tistruktuuriin siirtyväksi viestin vastaanottajalle. Kun  viesti  on  lähetetty,
  688. lähettäjä ei saa koskea siihen, ennen kuin  saa  viestin  takaisin  vastauksena,
  689. minkä jälkeen se taas kuuluu lähettäjälle.
  690.  
  691. Kun viesti saapuu viestiporttiin, se lisätään sen viestilistan perään. Kun vies-
  692. ti vastaanotetaan portista, se otetaan listan alusta. Näin viestit saadaan  aina
  693. saapumisjärjestyksessä. Viestin saapuminen viestiporttiin voi  aikaansaada  sig-
  694. naalin sen omistajalle tai aiheuttaa ohjelmallisen keskeytyksen.
  695.  
  696. Tässä tulevat määrittelyt viestiportille ja viestille: 
  697.  
  698. struct MsgPort {
  699.     struct  Node mp_Node;       /* Node */
  700.     UBYTE   mp_Flags;           /* liput */
  701.     UBYTE   mp_SigBit;          /* signaalibitin numero */
  702.     void   *mp_SigTask;         /* kohde, jota signaloidaan */
  703.     struct  List mp_MsgList;    /* viestilista  */
  704. };
  705.  
  706. struct Message {
  707.     struct  Node mn_Node;           /* Node */
  708.     struct  MsgPort *mn_ReplyPort;  /* vastausportti */
  709.     UWORD   mn_Length;              /* viestin pituus */
  710. };
  711.  
  712. SigTask sisältää osoittimeen  ohjelman,  jota  signaloidaan,  Task-struktuuriin.
  713. Mikäli tarkoitus on aiheuttaa keskeytys, se  onkin  osoitin  Interrupt-struktuu-
  714. riin. Nämä käsitellään myöhemmin. ReplyPort on osoitin lähettäjän omaan  viesti-
  715. porttiin. Viesti linkataan tämän portin viestilistaan, kun vastaanottaja  vastaa
  716. siihen. Pituus on Message-struktuurin pituus plus datablokin koko.
  717.  
  718. Viestiportin voi luoda varaamalla tarpeeksi muistia ja alustamalla  sen  kentät.
  719. Viestilista tulee ehdottomasti alustaa NewList()-funktiolla tai  NEWLIST-makrol-
  720. la. V36 tarjoaa myös funktion CreateMsgPort(), jolla portin voi luoda  automaat-
  721. tisesti. Tämän jälkeen portin voi tehdä julkiseksi, mutta usein se ei  ole  tar-
  722. peen. Julkinen portista tulee silloin, kun  se  lisätään  Execin  porttilistaan,
  723. josta toiset tehtävät voivat saada  osoittimen  siihen  ja  lähettää  tehtävälle
  724. viestejä.
  725.  
  726. V36:lla portti tehdään ja tuhotaan näin: 
  727.  
  728. struct MsgPort *mp;
  729.  
  730. if(mp = CreateMsgPort()) {
  731.     mp->mp_Node.ln_Name = "Portin nimi";   /* tarpeen vain julkisissa */
  732.     mp->mp_Node.ln_Pri = 2;                /* erityisen tärkeä portti */
  733.     AddPort(mp);                           /* lisätään portti listaan */
  734.  
  735.     /* Portin käyttöä */
  736.  
  737.     RemPort(mp);                           /* poistetaan portti listasta */
  738.     DeleteMsgPort(mp);                     /* ja tuhotaan se */
  739.  
  740. } else printf("Porttia ei saatu tehtyä.\n");
  741.  
  742. Nimeä ja prioriteettia tarvitaan vain, jos  portista  tehdään  julkinen  eli  se
  743. lisätään Execin porttilistaan. Porttilistaan lisätty portti pitää  aina  muistaa
  744. myös poistaa ennen sen tuhoamista. Tällaiseen porttiin lähetetään  toisesta  oh-
  745. jelmasta viesti näin: 
  746.  
  747. struct MsgPort *port;
  748. struct Message *msg;
  749.  
  750. if(port = FindPort("Portin nimi")) PutMsg(port,msg);
  751.  
  752. Portin löytyminen täytyy aina tarkistaa,  eikä  antaa  FindPort():n  palauttamaa
  753. osoitinta suoraan PutMsg():lle. Nyt siirrytään taas  vastaanottavaan  ohjelmaan.
  754. Viesti otetaan vastaan ja siihen vastataan seuraavasti: 
  755.  
  756. struct MsgPort *mp;
  757. struct Message *msg;
  758.  
  759. while(!(port = GetMsg(mp))) WaitPort(mp);
  760.  
  761. /* viestin käsittelyä */
  762.  
  763. ReplyMsg(msg);
  764.  
  765. Tässä viestiä odotetaan WaitPort()-funktiolla. Mikäli tarvitsee odottaa  jotakin
  766. muutakin, joudutaan käyttämään Wait()-funktiota, kuten yleensä on  asian  laita.
  767. Tällöin kyseeseen tuleva toimintatapa on selvitetty aiemmin tässä  artikkelissa,
  768. mutta tässä on vielä esimerkki, jossa odotetaan viestiä ja breikkiä: 
  769.  
  770. portsigmask = 1L << mp->mp_SigBit;
  771.  
  772. Wait(portsigmask | SIGBREAKF_CTRL_C);
  773.  
  774. Lopuksi tässä ovat vielä Execin viesteihin liittyvät funktiot:
  775.  
  776. void AddPort( struct MsgPort *port );
  777.  
  778. Lisää viestiportin porttilistaan tehden siitä  julkisen,  jolloin  mikä  tahansa
  779. järjestelmässä ajettava ohjelma voi etsiä sen ja lähettää siihen viestin. 
  780.  
  781. void RemPort( struct MsgPort *port );
  782.  
  783. Poistaa julkisen portin Execin porttilistasta. Tämä on toimenpide, joka on muis-
  784. tettava aina tehdä ennen portin tuhoamista tai poistumista ohjelmasta. 
  785.  
  786. void PutMsg( struct MsgPort *port, struct Message *message );
  787.  
  788. Lähettää viestin eli laittaa sen kohdeporttiin.
  789.  
  790. struct Message *GetMsg( struct MsgPort *port );
  791.  
  792. Vastaanottaa viestin portista.
  793.  
  794. void ReplyMsg( struct Message *message );
  795.  
  796. Vastaa viestiin eli lähettää vastaanotetun viestin takaisin lähettäjälle.
  797.  
  798. struct Message *WaitPort( struct MsgPort *port );
  799.  
  800. Odottaa viestiä saapuvaksi porttiin ja palaa vasta sitten, kun sellainen  tulee.
  801. Yleensä  on  kuitenkin  tarve  odottaa  useita  signaaleja,  jolloin   joudutaan
  802. käyttämään WaitPort():n sijasta Wait():ia, jolle on silloin  rakennettava  maski
  803. portin signaalibitistä. 
  804.  
  805. struct MsgPort *FindPort( UBYTE *name );
  806.  
  807. Etsii porttia porttilistasta ja palauttaa osoittimen porttiin, jonka nimi täsmää
  808. annettuun merkkijonoon, tai nollan, jos halutunnimistä porttia ei ollut  listal-
  809. la. 
  810.  
  811.  
  812.  
  813.  
  814. {3Kirjastot
  815. {3---------
  816.  
  817. Moneen otteeseen on jo ollut puhetta kirjastoista.  Ne  ovat  funktiokasautumia,
  818. jotka  liittyvät  tiettyyn  osa-alueeseen.  Kirjastojen   funktioita   kutsutaan
  819. käyttäen niiden kantaosoitetta, joka saadaan  avaamalla  kirjastot.  Käytettävät
  820. kirjastot on aina avattava ja lopuksi suljettava.  DICE  osaa  avata  tärkeimmät
  821. kirjastot automaattisesti, kun se havaitsee  viittauksen  niiden  kantaosoitteen
  822. nimeen.
  823.  
  824. Tässä on tärkeimpien kirjastojen osoittimien (library base pointer) nimiä: 
  825.  
  826.          asl.library                AslBase
  827.          commodities.library        CxBase
  828.          dos.library                DOSBase
  829.          exec.library               SysBase
  830.          expansion.library          ExpansionBase
  831.          graphics.library           GfxBase
  832.          icon.library               IconBase
  833.          iffparse.library           IFFParseBase
  834.          intuition.library          IntuitionBase
  835.          layers.library             LayersBase
  836.          utility.library            UtilityBase
  837.          version.library            <salainen>
  838.          workbench.library          WorkbenchBase
  839.  
  840. Kirjasto koostuu funktioiden hyppykäskytaulukosta ja kirjastostruktuurista:
  841.  
  842.          Kantaosoite + sizeof Lib   kirjastokohtaista tietoa
  843.          Kantaosoite                struct Library
  844.          Kantaosoite - 6            JMP Func1
  845.          Kantaosoite - 12           JMP Func2
  846.          Kantaosoite - 18           JMP Func3
  847.          ...
  848.  
  849. Kantaosoitteessa on  siis  kirjaston  node,  Library-struktuuri,  jonka  perässä
  850. yleensä tulee lisää kirjastokohtaista tietoa. Hyppytaulukko, kuten jo  mainittu,
  851. on kantaosoitteesta taaksepäin. Ensimmäiseen funktioon hyppäävä käsky on -6:ssa,
  852. joka on sen LVO eli vektorioffset. Funktiota kutsutaan hyppäämällä tähän  osoit-
  853. teeseen: 
  854.  
  855.          jsr      -6(a6)
  856.  
  857. Kantaosoitteen tulee olla a6:ssa. Ensimmäiset neljä funktiota on varattu:
  858.  
  859.          LVO      Funktio  Tarkoitus
  860.  
  861.          -6       Open     Avaa kirjaston (OpenLibrary() kutsuu)
  862.          -12      Close    Sulkee kirjaston (CloseLibrary() kutsuu)
  863.          -18      Expunge  Poistaa kirjaston muistista, jos ei käyttäjiä
  864.          -24      Res.     Ei toimintoa, varattu paikka, palauttaa nollan
  865.  
  866. Näitä funktioita ohjelman ei tavallisesti tarvitse koskaan kutsua.  Exec  kutsuu
  867. niitä (lisää tietoa myöhemmin). Ensimmäinen varsinainen  funktio  on  offsetissä
  868. -30, seuraava on -36 ja niin edelleen. C-kielellä ohjelmoitaessa näistä asioista
  869. ei tarvitse huolehtia ollenkaan. Kirjasto avataan näin: 
  870.  
  871. struct Library *IntuitionBase;
  872.  
  873. if(!(IntuitionBase = OpenLibrary("intuition.library",LIBRARY_VERSION))) {
  874.     printf("Intuition ei auennut\n");
  875.     exit(RETURN_FAIL);
  876. }
  877.  
  878. Rakenteellisesti parempi tapa olisi:
  879.  
  880. if(IntuitionBase = OpenLibrary("intuition.library",LIBRARY_VERSION)) {
  881.  
  882.     ...<Intuitionin käyttöä>...
  883.  
  884. } else printf("Intuition ei auennut\n");
  885.  
  886. Intuition on siinä määrin keskeinen kirjasto, että jos se ei aukea,  on  järjes-
  887. telmä jo aika nurin. Kirjasto suljetaan lopuksi näin: 
  888.  
  889. CloseLibrary(IntuitionBase);
  890.  
  891. Jos kutsut cleanup-koodia myös virhetilanteissa, on tarkistus tarpeen:
  892.  
  893. if(IntuitionBase) CloseLibrary(IntuitionBase);
  894.  
  895. Jos et ole kiinnostunut kirjastojen tekemisestä, katso luvun lopusta kirjastojen
  896. käyttöön liittyvien funktioiden selitykset ja hyppää seuraavaan lukuun.
  897.  
  898. Kirjastojen tekeminen
  899.  
  900. Jos olet kirjoittanut joukon funktioita,  joita  käytetään  useissa  ohjelmissa,
  901. kannattaa harkita niiden sijoittamista kirjastoon. Kerron  nyt  lyhyesti,  miten
  902. kirjasto tehdään. Ensin katsotaan kantaosoitteessa olevaa struktuuria: 
  903.  
  904. struct Library {
  905.     struct  Node lib_Node;
  906.     UBYTE   lib_Flags;
  907.     UBYTE   lib_pad;
  908.     UWORD   lib_NegSize;            /* Kirjaston koko alas päin */
  909.     UWORD   lib_PosSize;            /* Kirjaston koko ylös päin */
  910.     UWORD   lib_Version;            /* versionumero */
  911.     UWORD   lib_Revision;           /* merkityksettömämpi revisionumero */
  912.     APTR    lib_IdString;           /* Kirjaston tunniste */
  913.     ULONG   lib_Sum;                /* Kirjaston tarkistussumma */
  914.     UWORD   lib_OpenCnt;            /* Käyttäjien määrä */
  915. };
  916.  
  917. Tätä kirjastonodea ei sisällytetä segmenttiin, vaan se tehdään lennossa  kirjas-
  918. toa ladattaessa. Tiedot toki saadaan kirjastotiedostosta. IdString on  kirjaston
  919. tunniste, joka on muodossa "nimi versio.revisio (päiväys)". Lopussa  tulee  olla
  920. ensin CR ja sitten LF. Merkkijonon tulee päättyä nollatavuun. Tässä on esimerkki
  921. kirjaston idauksesta: 
  922.  
  923. graphics 40.8 (15.3.93)
  924.  
  925. Tässä tapauksessa kirjaston  versionumero  on  40  (lib_Version)  ja  revisio  8
  926. (lib_Revision). Exec ylläpitää kirjastosta tarkistussummaa (lib_Sum).  Kirjaston
  927. koko pidetään kahdessa  muuttujassa,  erikseen  koko  kantaosoitteesta  alaspäin
  928. (hyppytaulukko) ja ylöspäin (datastruktuuri). Kirjaston koodi on  eri  paikassa.
  929. Kirjasto on aina tehtävä konekielellä - ainakin sen runko. Tässä  on  lähtökohta
  930. siihen: 
  931.  
  932.  STRUCTURE RT,0
  933.     UWORD RT_MATCHWORD              ; tunniste
  934.     APTR  RT_MATCHTAG               ; osoitin siihen...
  935.     APTR  RT_ENDSKIP                ; lopun osoitin
  936.     UBYTE RT_FLAGS                  ; lippuja
  937.     UBYTE RT_VERSION                ; versionumero
  938.     UBYTE RT_TYPE                   ; moduulin tyyppi (kirjasto)
  939.     BYTE  RT_PRI                    ; prioriteetti
  940.     APTR  RT_NAME                   ; osoitin moduulin nimeen
  941.     APTR  RT_IDSTRING               ; osoitin moduulin idaukseen
  942.     APTR  RT_INIT                   ; alustusosoitin
  943.     LABEL RT_SIZE
  944.  
  945. Tämä Resident-struktuuri määrittää moduulin. Moduuli on tässä  tapauksessa  kir-
  946. jasto, mutta se voi olla muukin,  esimerkiksi  laiteohjain.  RomTag  (RT)  alkaa
  947. matchwordilla, jonka arvon tulee olla $4AFC. Siitä se tunnistetaan.  Seuraavaksi
  948. tulee osoitin tuohon sanaan ja sitten osoitin, jota käytetään lähinnä  moduulien
  949. skannaukseen ROMissa. Sen tulisi osoittaa koodin loppuun. Muut kentät ovat itse-
  950. kuvaavia.
  951.  
  952. Liput ovat yleensä RTF_AUTOINIT. Tämä on erityinen alustusmoodi.  Jos  AUTOINIT-
  953. lippu ei ole päällä, Exec ei alusta kirjastoasi automaattisesti. Sen sijaan kut-
  954. sutaan funktiota, jonka osoitin  on  RT_INIT:ssä.  Tämän  funktion  tulee  tehdä
  955. myöhemmin kerrottavat alustustoimenpiteet. Kannattaa kuitenkin hyödyntää  AUTOI-
  956. NIT-toimintoa. Kun se on päällä, onkin RT_INIT:ssä osoitin taulukkoon: 
  957.  
  958.          dataSize          Kirjaston data-alueen koko
  959.          vectors           Osoitin funktiotaulukkoon
  960.          structure         Osoitin InitStruct()-dataan
  961.          initFunction      Osoitin alustusfunktioon
  962.  
  963. Data-alueen koko käsittää Library-struktuurin sekä mahdolliset omat lisät. Funk-
  964. tiotaulukko koostuu joko longword-osoitteista  kirjaston  funktioihin,  tai  en-
  965. simmäisen wordin ollessa -1, word-offseteista funktioiden  alkuun  suhteellisina
  966. taulukon alkuun. Jälkimmäinen tapa voi  olla  hieman  hankalampi  implementoida,
  967. mutta se on tehokkaampi.
  968.  
  969. Exec kutsuu myös InitStruct()-funktiota, jolla alustetaan Library-node  ja  muut
  970. mahdolliset omat datat. Alustusfunktiota kutsutaan sen jälkeen, kun kirjasto  on
  971. kunnossa. Sillä ei ole enää paljon tehtävää, sille jää lähinnä vain kirjanpidol-
  972. lisia tehtäviä sekä tietysti tarvittaessa alustettavat kirjaston omat datat.
  973.  
  974. Aikaisemmin mainitut neljä ensimmäistä funktiota ovat pakollisia jokaisessa kir-
  975. jastossa. Open():n tehtävä on lähinnä kasvattaa opencountia ja varata  avaajalle
  976. käyttäjäkohtaisia data-alueita, jos niille on  tarvetta  tms.  Close()  vähentää
  977. opencountia ja vapauttaa varaukset ym. Expunge()  poistaa  kirjaston  muistista.
  978. Sitä varten pitää vapauttaa muistialueet ja poistaa kirjaston segmentit muistis-
  979. ta.
  980.  
  981. Reserved()-funktio on myös pakko implementoida. Sen tulee palauttaa nolla.  Olen
  982. nähnyt kirjaston lähdekoodin, jossa Reserved()-funktion paikalle hyppytaulukkoon
  983. laitettiin nolla. Tuloksena on varmuudella koneen kaatuminen tai  muu  häiriöti-
  984. lanne, kun tälle funktiolle keksitään käyttöä,  ja  tulevaisuuden  käyttöjärjes-
  985. telmä kutsuu sitä - ja tällaisilla kirjastoilla hyppää tuohon osoitteeseen.
  986.  
  987. Kirjaston tekeminen ei välttämättä ole helppoa, joten laitan tähän hieman koodia
  988. esimerkiksi. Tämä tulee suoraan sh.libraryn lähdekoodista. Poistin  tosin  funk-
  989. tiomäärittelyt - laitoin vain  muutaman  esimerkiksi,  jotta  ne  eivät  sotkisi
  990. tässä. Segmentti alkaa pienellä koodinpätkällä, jonka tarkoitus on estää kirjas-
  991. ton ajaminen ohjelmana. RTC_MATCHWORD on laiton käsky, jolloin sen ollessa  tie-
  992. doston alussa kone vain kaatuisi nätisti,  mutta  pienellä  ylimääräisellä  koo-
  993. dinpätkällä sekin voidaan välttää. 
  994.  
  995. VER   equ   10    ; versionumero
  996. REV   equ   297   ; revisionumero
  997.  
  998. start moveq #-1,d0
  999.       rts
  1000.  
  1001. RTAG  DC.W RTC_MATCHWORD
  1002.       DC.L RTAG,FINAL
  1003.       DC.B RTF_AUTOINIT,VER,NT_LIBRARY,0
  1004.       DC.L LNAME,IDSTR,INITD
  1005.  
  1006. INITD DC.L sh_base_sizeof,funcs,datat,initc
  1007.  
  1008. funcs dc.w -1,open-funcs,close-funcs,expunge-funcs,extfunc-funcs
  1009.       dc.w func1-funcs,func2-funcs,-1
  1010.  
  1011. ; Tämä on InitStruct():n datataulukko. INIT#?-makroilla tehdään sille
  1012. ; "komentojono", jolla alustetaan noden kentät. Lisää tietoa InitStruct():n
  1013. ; toiminnasta on include-tiedostossa "exec/initializers.i". Se käsitellään
  1014. ; ehkä joskus.
  1015.  
  1016. datat INITBYTE LN_TYPE,NT_LIBRARY
  1017.       INITLONG LN_NAME,LNAME
  1018.       INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
  1019.       INITWORD LIB_VERSION,VER
  1020.       INITWORD LIB_REVISION,REV
  1021.       INITLONG LIB_IDSTRING,IDSTR
  1022.       DC.W 0
  1023.  
  1024. ; Tämä on alustusfunktio, jota OpenLibrary() kutsuu sen jälkeen, kun
  1025. ; kirjasto on pantu pystyyn. Tallennan segmentin osoitteen (BCPL-muodossa)
  1026. ; sekä avaan tarvittavat kirjastot. OpenLib on yksinkertainen makro,
  1027. ; joka kutsuu OpenLibrary():ä ja moveaa basepointterin data-alueelle
  1028. ; kirjastonoden perään (sh-struktuuri pitää sisällään Library-noden
  1029. ; sekä kirjaston omia muuttujia). ASL:sta ja Commoditiesista edellytetään
  1030. ; versio 37 tai suurempi.
  1031.  
  1032. initc movem.l a4/a6,-(sp)
  1033.       movea.l d0,a4
  1034.       move.l a0,sh_SegList(a4)
  1035.       movea.l 4,a6
  1036.       move.l a6,sh_SysBase(a4)
  1037.       OpenLib Dos,sh_DosBase(a4)             ; DOS
  1038.       OpenLib intuition,sh_IntuitionBase(a4) ; Intuition
  1039.       OpenLib AName,sh_AslBase(a4),37        ; ASL
  1040.       OpenLib CName,sh_CxBase(a4),37         ; Commodities
  1041.       move.l a4,d0
  1042.       movem.l (sp)+,a4/a6
  1043.       rts
  1044.  
  1045. ; Nämä ovat Open() ja Close(). Ne vain päivittävät opencountin. Open()
  1046. ; palauttaa kirjaston kantaosoitteen, joka on sitä kutsuttaessa a6:ssa.
  1047. ; Close() tarkistaa lisäksi DELEXP-bitin arvon. Se tarkoittaa, että
  1048. ; "delayed expunge" on päällä, eli joku on kutsunut Expunge():a sillä
  1049. ; aikaa, kuin kirjastoa on käytetty (katso alla). Jos DELEXP on päällä,
  1050. ; suoritus lasketaan läpi Expunge()-funktioon.
  1051.  
  1052. open  addq.w #1,LIB_OPENCNT(a6)
  1053.       bclr.b #LIBB_DELEXP,LIB_FLAGS(a6)
  1054.       move.l a6,d0
  1055.       rts
  1056.  
  1057. close subq.w #1,LIB_OPENCNT(a6)
  1058.       btst.b #LIBB_DELEXP,LIB_FLAGS(a6)
  1059.       beq extfunc
  1060.  
  1061. ; Expunge() poistaa kirjaston muistista. Ensin varmistetaan, että kirjasto
  1062. ; ei ole kenelläkään auki. Jos näin on, ei sitä tietenkään poisteta, vaan
  1063. ; asetetaan DELEXP-lippu viivästetyn expungen merkiksi. Kirjasto expungetaan
  1064. ; heti, kun viimeinen sen käyttäjä sulkee sen. Muussa tapauksessa suljetaan
  1065. ; kirjastot, vapautetaan data-alueen käyttämät muistialueet ja poistetaan
  1066. ; segmentti muistista. Minun mielestäni tämä on kyllä hieman vaarallista,
  1067. ; mutta näin se neuvotaan tekemään.
  1068.  
  1069. expunge
  1070.  
  1071.       movem.l d2/a5/a6,-(sp)
  1072.       clr.l d2
  1073.       movea.l a6,a5
  1074.       movea.l 4,a6
  1075.       tst.w LIB_OPENCNT(a5)
  1076.       beq expu0
  1077.       bset.b #LIBB_DELEXP,LIB_FLAGS(a5)
  1078.       bra expu2
  1079. expu0 CloseLib sh_DosBase(a5)
  1080.       CloseLib sh_IntuitionBase(a5)
  1081.       move.l sh_CxBase(a5),d0
  1082.       beq expu3
  1083.       movea.l d0,a1
  1084.       Lib CloseLibrary
  1085. expu3 move.l sh_AslBase(a5),d0
  1086.       beq expu1
  1087.       movea.l d0,a1
  1088.       Lib CloseLibrary
  1089. expu1 move.l sh_SegList(a5),d2
  1090.       movea.l a5,a1
  1091.       Lib Remove
  1092.       clr.l d0
  1093.       movea.l a5,a1
  1094.       move.w LIB_NEGSIZE(a5),d0
  1095.       suba.l d0,a1
  1096.       add.w LIB_POSSIZE(a5),d0
  1097.       Lib FreeMem
  1098. expu2 move.l d2,d0
  1099.       movem.l (sp)+,d2/a5/a6
  1100.       rts
  1101.  
  1102. extfunc
  1103.  
  1104.       clr.l d0
  1105.       rts
  1106.  
  1107. Kirjaston lataaminen
  1108.  
  1109. Kirjaston voi ottaa käyttöön käsin lataamalla segmentin LoadSeg():llä (DOS-funk-
  1110. tio), valmistelemalla sen MakeLibrary():llä ja lisäämällä sen  Execin  kirjasto-
  1111. listaan AddLibrary():llä. Helpompaa on kuitenkin käyttää yllä kuvattua  RomTagia
  1112. (Resident-struktuuria), jolloin OS osaa ladata segmentin automaattisesti, kun se
  1113. on sijoitettu LIBS:-hakemistoon, aina kun joku sitä haluaa käyttää (kutsuu Open-
  1114. Library():ä).
  1115.  
  1116. Kirjasto voi liittyä myös Zorro-korttiin tai muuhun  laajennukseen,  jolloin  se
  1117. voidaan sijoittaa SYS:Expansion-hakemistoon. Tällöin startup-sequencessa ajetta-
  1118. va BindDrivers-komento lataa sen. Kortin toiminnasta huolehtiva kirjasto voidaan
  1119. myös sijoittaa kortilla olevaan ROMiin. Mutta  tämä  kuuluu  Expansion-kirjaston
  1120. toimialaan, joten siitä ei enempää tässä.
  1121.  
  1122. Nämä ovat Execin tarjoamat kirjastofunktiot: 
  1123.  
  1124. void AddLibrary( struct Library *library );
  1125.  
  1126. Lisää kirjaston Execin kirjastolistaan. Normaalisti Exec itse kutsuu tätä  funk-
  1127. tiota, kun joku haluaa avata levyllä olevan sen jälkeen, kun se on ensin  kutsu-
  1128. nut MakeLibrary():ä. Viimeksi mainittua en käsittele tässä ollenkaan,  koska  se
  1129. kuuluu moduulinluontifunktioihin. Näihin palataan ehkä myöhemmin kurssin aikana.
  1130.  
  1131. void RemLibrary( struct Library *library );
  1132.  
  1133. Poistaa kirjaston Execin kirjastolistasta. 
  1134.  
  1135. struct Library *OldOpenLibrary( UBYTE *libName );
  1136.  
  1137. Vanha avaamisfunktio. Ei tule käyttää. 
  1138.  
  1139. struct Library *OpenLibrary( UBYTE *libName, unsigned long version );
  1140.  
  1141. Avaa kirjaston. Parametreinä annetaan kirjaston nimi ja versionumero.  Versionu-
  1142. meron tulee olla sellainen, että kirjastossa varmasti on kaikki funktiot,  joita
  1143. tullaan kutsumaan. Mikäli kyseessä on käyttöjärjestelmän kirjasto, tulee versio-
  1144. numeron olla LIBRARY_VERSION, joka on tätä nykyä 33. Se on  vanhin  virallisesti
  1145. tuettu versionumero. 
  1146.  
  1147. void CloseLibrary( struct Library *library );
  1148.  
  1149. Sulkee kirjaston. Parametrinä annetaan kirjaston basepointer.
  1150.  
  1151. APTR SetFunction( struct Library *library, long funcOffset,
  1152.     unsigned long (*newFunction)() );
  1153.  
  1154. Vaihtaa kirjaston funktion osoittimen. Tällä funktiolla voidaan kirjaston  alku-
  1155. peräinen funktiokoodi korvata omalla koodilla.  Vektori  vaihdetaan  niin,  että
  1156. tarkistussumma on aina oikein (katso alla).  Tällä  funktiolla  ei  voi  vaihtaa
  1157. epästandardien kirjastojen (esimerkiksi dos.library) vektoreita.
  1158.  
  1159. Funktiolle annetaan osoitin uuteen funktiokoodiin, ja sen jälkeen, kun  kyseistä
  1160. funktiota kutsutaan, suoritetaan uusi koodi. Mikäli uusi koodi ei jää  muistiin,
  1161. vaan lähtee pois esimerkiksi ohjelman suorituksen loputtua,  tulee  alkuperäinen
  1162. vektori palauttaa. SetFuntion() palauttaa vanhan koodin osoitteen, joten  se  on
  1163. helppo toteuttaa. 
  1164.  
  1165. void SumLibrary( struct Library *library );
  1166.  
  1167. Laskee kirjaston noden tarkistussumman. Funktio päivittää  tarkistussumman  kir-
  1168. jaston nodestruktuuriin. Funktio myös tarkistaa vanhan summan, ja mikäli  se  ei
  1169. täsmää, näyttää alertin. Aiheettomien alertien välttämiseksi  kirjasto  on  mer-
  1170. kittävä muutetuksi aina, kun sitä muutetaan. Tätä varten on lippu  CHANGED.  Oh-
  1171. jelmat eivät yleensä kutsu tätä funktiota. 
  1172.  
  1173.  
  1174. {3Laiteohjaimet ja Execin I/O-toiminnot
  1175. {3-------------------------------------
  1176.  
  1177. Amigassa I/O-toiminnoista vastaavat Execin laiteohjaimet eli  devicet.  Laiteoh-
  1178. jaimet ovat suoraan tekemisissä hardwaren kanssa. Ohjelmat  käyttävät  hardwarea
  1179. laiteohjaimien kautta. Exec tarjoaa helpon liittymän laiteohjaimien  hyödyntämi-
  1180. seen. I/O-toiminnot tehdään lähettämällä laiteohjaimelle komentoja ja  mahdolli-
  1181. sesti dataa sekä otetaan niitä vastaan siltä.
  1182.  
  1183. Käyttöjärjestelmään kuuluu 14 laiteohjainta: 
  1184.  
  1185.          audio.device               äänet
  1186.          clipboard.device           leikkuulauta
  1187.          console.device             konsoli (näppis ja näytin)
  1188.          gameport.device            ilotikku, paddlet ja hiiri
  1189.          input.device               syöte (monista lähteistä)
  1190.          keyboard.device            näppis (matalalla tasolla)
  1191.          narrator.device            puhe, selostus
  1192.          parallel.device            rinnakkaisportti
  1193.          printer.device             kirjoitin
  1194.          scsi.device                Commodoren SCSI-väylä
  1195.          serial.device              sarjaportti
  1196.          timer.device               ajastin
  1197.          trackdisk.device           sisäänrakennettu MFM-levyasemaväylä
  1198.  
  1199. Laiteohjaimiin tutustutaan tarkemmin joskus muulloin. Tässä  on  tarkoitus  vain
  1200. nopeasti katsoa, miten niitä käytetään. Laiteohjain on  avattava  ennen  käyttöä
  1201. aivan kuin kirjastokin. Laiteohjaimen käyttöä varten tarvitsemme viestiportin ja
  1202. IORequestin: 
  1203.  
  1204. struct IORequest {
  1205.     struct  Message io_Message;
  1206.     struct  Device  *io_Device;     /* Device-struktuuri            */
  1207.     struct  Unit    *io_Unit;       /* Unit-struktuuri (privaatti)  */
  1208.     UWORD   io_Command;             /* I/O-komento                  */
  1209.     UBYTE   io_Flags;               /* Liput (IOF_QUICK)            */
  1210.     BYTE    io_Error;               /* Virhenumero                  */
  1211. };
  1212.  
  1213. Tällä struktuurilla voidaan lähettää  vain  komentoja,  joihin  ei  liity  datan
  1214. lähettämistä tai vastaanottamista. Jos dataakin liikkuu, tarvitaan pidempi  ver-
  1215. sio: 
  1216.  
  1217. struct IOStdReq {
  1218.     struct  Message io_Message;
  1219.     struct  Device  *io_Device;     /* Device-struktuuri            */
  1220.     struct  Unit    *io_Unit;       /* Unit-struktuuri (privaatti)  */
  1221.     UWORD   io_Command;             /* I/O-komento                  */
  1222.     UBYTE   io_Flags;               /* Liput (IOF_QUICK)            */
  1223.     BYTE    io_Error;               /* Virhenumero                  */
  1224.     ULONG   io_Actual;              /* Siirtyneiden tavujen määrä   */
  1225.     ULONG   io_Length;              /* Tavujen määrä                */
  1226.     APTR    io_Data;                /* Osoittaa dataan              */
  1227.     ULONG   io_Offset;              /* Offset laitteella            */
  1228. };
  1229.  
  1230. Laiteohjainta avatessa tarvitsee huolehtia vain siitä, että  viestiosuus  struk-
  1231. tuurista on alustettu kunnolla. Tietysti kenttien tulee olla nollattu.  Laiteoh-
  1232. jain alustaa Device- ja Unit-kentät,  ja  muut  alustetaan  myöhemmin  komentoja
  1233. lähetettäessä. Käyttis V36 tarjoaa käteviä apufunktioita viestiportin  ja  IORe-
  1234. questin tekemiseen. Laiteohjain aukeaa näin: 
  1235.  
  1236. struct MsgPort *port;
  1237. struct IOStdReq *request;
  1238.  
  1239. if(!(request = CreateIORequest(port = CreateMsgPort(),
  1240.     sizeof(struct IOStdReq)))) {
  1241.     printf("IORequestin tekeminen epäonnistui\n");
  1242.     DeleteMsgPort(port);
  1243. }
  1244.  
  1245. if(OpenDevice("trackdisk.device",0,request,0)) printf("Ei auennut\n");
  1246.     else {
  1247.  
  1248.     ....<trackdisk.devicen käyttöä>...
  1249.  
  1250. }
  1251.  
  1252. Huomattavaa tässä on erityisesti se, että OpenDevice() palauttaa nollan silloin,
  1253. kun laiteohjaimen avaaminen onnistui. Yleensähän nolla tarkoittaa virhettä, mut-
  1254. ta tässä se on toisin päin. Virheen tapauksessa palautetaan laiteohjainkohtainen
  1255. virhenumero. Toinen parametri on yksikön numero. Tässä tapauksessa se tarkoittaa
  1256. levyasemaa, nolla on (ensimmäinen) sisäinen  levari.  Viimeisenä  voidaan  antaa
  1257. lippuja. Lopuksi laiteohjain pitää sulkea: 
  1258.  
  1259. CloseDevice(request);
  1260. DeleteIORequest(request);
  1261. DeleteMsgPort(port);
  1262.  
  1263. Jokainen laiteohjain tunnistaa kahdeksan vakiokomentoa:
  1264.  
  1265.          CMD_RESET         Resetoi laitteen alkutilaansa
  1266.          CMD_READ          Lukee dataa laitteelta
  1267.          CMD_WRITE         Kirjoittaa dataa laitteelle
  1268.          CMD_UPDATE        Päivittää tietoja
  1269.          CMD_CLEAR         Tyhjentää laiteohjaimen puskurit
  1270.          CMD_STOP          Pysäyttää laitteen
  1271.          CMD_START         Käynnistää laitteen
  1272.          CMD_FLUSH         Puhdistaa komentojonon
  1273.  
  1274. Kaikki laiteohjaimet eivät välttämättä tue kaikkia näitä, mikä on minusta varsin
  1275. kummallista. Tuntemattoman laiteohjaimen käyttäminen voi olla  vaarallista.  Ko-
  1276. mennon lähettäminen laiteohjaimelle on helppoa: 
  1277.  
  1278. request->io_Command = CMD_CLEAR;
  1279. DoIO(request);
  1280.  
  1281. Nämä peruskomennot  toteuttavat  yksinkertaisia  toimintoja.  Esimerkiksi  FLUSH
  1282. abortoi kaikki laiteohjaimella jonossa olevat I/O-toimintopyynnöt - myös  muiden
  1283. ohjelmien, joten sen kanssa kannattaa olla varovainen. CLEAR tyhjentää sarjapor-
  1284. tin puskurit. UPDATE päivittää levylle trackdisk.devicen muistissa olevan  uran,
  1285. jos sitä on muutettu. STOP pysäyttää laitteen  toiminnan.  Laiteohjain  lopettaa
  1286. kyseisen yksikön jonossa olevien pyyntöjen  käsittelyn  ja  jatkaa  toimintaansa
  1287. vasta, kun saa START-komennon. Moniyksikköisessä laitteessa komennot vaikuttavat
  1288. vain siihen yksikköön (esimerkiksi nimenomaiseen  levyasemaan),  jolle  komennot
  1289. annetaan.
  1290.  
  1291. Kun halutaan esimerkiksi kirjoittaa dataa, täytyy requestiin liittää tiedot kir-
  1292. joitettavasta datasta, missä se on ja paljonko sitä  on.  Levykkeen  tapauksessa
  1293. pitää myös ilmoittaa, minne se kirjoitetaan. Kun kyseessä  on  massamuistiasema,
  1294. muuttuja Offset kertoo datan sijaintipaikan levyllä: 
  1295.  
  1296. APTR data;
  1297.  
  1298. request->io_Data = data;            /* osoitin dataan */
  1299. request->io_Length = 0x200;         /* kirjoitetaan vain yksi sektori */
  1300. request->io_Offset = 0x20000;       /* paikka levyllä tavuina */
  1301. request->io_Command = CMD_WRITE;
  1302. DoIO(request);
  1303.  
  1304. Tämä koodi kirjoittaa 512 tavua dataa levyllä kohtaan $20000.  Sektorin  numero,
  1305. jonka sisältö korvautuu uudella datalla, saadaan jakamalla Offset 512:lla.  Off-
  1306. setin ja pituuden tulee olla jaollisia sektorin koolla, joka tässä oletuksena on
  1307. 512 tavua. Otin tässä trackdisk.devicen vain yksinkertaiseksi esimerkiksi - mas-
  1308. samuistin käyttäminen laiteohjaintasolla ei ole suotavaa.
  1309.  
  1310. DoIO() on synkroninen funktio. Se jää odottamaan vastausta ja ottaa viestin sit-
  1311. ten portista. SendIO() vain lähettää viestin, joten vastausta on itse odotettava
  1312. ja otettava viesti vastausportista. Sitä  odotellessa  voi  myös  kysellä,  onko
  1313. pyyntö jo käsitelty. Komennon voi myös abortoida. Tässä on esimerkki: 
  1314.  
  1315. SendIO(request);
  1316.  
  1317. while(TRUE) {
  1318.  
  1319.         ...<muuta juttua, jossa kuluu hieman aikaa>...
  1320.  
  1321.     if(CheckIO(request)) {
  1322.         GetMsg(port);
  1323.         break;
  1324. }
  1325.  
  1326. Voisihan tuota GetMsg():tä tietysti kutsua suoraankin, mutta näin se näyttää ki-
  1327. vemmalta... Jos on tarve keskeyttää operaatio: 
  1328.  
  1329. if(!(CheckIO(request))) {
  1330.     AbortIO(request);
  1331.     WaitIO(request);
  1332. }
  1333.  
  1334. Tämä koodinpätkä tutkii, vieläkö komento on kesken. Jos se on, abortoidaan se ja
  1335. odotetaan, että se todellakin on abortoitu.
  1336.  
  1337. Laiteohjaimen tekeminen
  1338.  
  1339. Tähän asiaan en paneudu nyt tässä.  Laiteohjaimet  tullaan  käsittelemään  omana
  1340. osanaan tällä kurssilla. Siinä yhteydessä teemme myös oman  laiteohjaimen.  Voin
  1341. tässä kuitenkin johdantona valottaa prosessia hieman. Laiteohjain on  toiminnal-
  1342. taan melko lailla samanlainen kuin kirjasto. Myös sillä on kantaosoite, io_Devi-
  1343. ce osoittaa Device-struktuuriin, joka on aivan samanlainen  kuin  Library-struk-
  1344. tuuri.
  1345.  
  1346. Myös laiteohjaimilla on hyppytaulukko, ja jotkin laiteohjaimet tarjoavat kirjas-
  1347. tojentyylisen funktioliittymän. Pääasiassa toimet kuitenkin hoidetaan  IOReques-
  1348. tien avulla. Laiteohjaimen on yleensä tarpeen  laukaista  lapsitehtävä  erikseen
  1349. jokaista yksikköä varten. Laiteohjaimen tekeminen on hankalampaa kuin kirjaston.
  1350. Perehdymme siihen hamassa tulevaisuudessa.
  1351.  
  1352. Nämä ovat Execin I/O-funktiot: 
  1353.  
  1354. void AddDevice( struct Device *device );
  1355.  
  1356. Lisää laiteohjaimen Execin listaan.
  1357.  
  1358. void RemDevice( struct Device *device );
  1359.  
  1360. Poistaa laiteohjaimen.
  1361.  
  1362. BYTE OpenDevice( UBYTE *devName, unsigned long unit,
  1363.     struct IORequest *ioRequest, unsigned long flags );
  1364.  
  1365. Avaa laiteohjaimen. Parametreinä annetaan sen nimi ja yksikkö, osoitin  asianmu-
  1366. kaisesti alustettuun requestblokkiin ja liput. 
  1367.  
  1368. void CloseDevice( struct IORequest *ioRequest );
  1369.  
  1370. Sulkee laiteohjaimen.
  1371.  
  1372. BYTE DoIO( struct IORequest *ioRequest );
  1373.  
  1374. Lähettää komennon laiteohjaimelle synkronisesti.
  1375.  
  1376. void SendIO( struct IORequest *ioRequest );
  1377.  
  1378. Lähettää komennon laiteohjaimelle asynkronisesti.
  1379.  
  1380. BOOL CheckIO( struct IORequest *ioRequest );
  1381.  
  1382. Tarkistaa komennon suorituksen. Palauttaa nollan, jos komento ei ole vielä  val-
  1383. mis. 
  1384.  
  1385. BYTE WaitIO( struct IORequest *ioRequest );
  1386.  
  1387. Odottaa komennon valmistumista. Voidaan kutsua esimerkiksi  AbortIO():n  jälkeen
  1388. tai suoraan SendIO():n jälkeen toiminnan synkronoimiseksi. Huomaa, että WaitIO()
  1389. myös ottaa viestin vastausportista. Sitä voidaankin käyttää  myös  jo  valmiiksi
  1390. tiedetyn I/O-requestin valmisteluun seuraavaa tehtävää varten. WaitIO()  odottaa
  1391. viestiä porttiin kutsumalla Wait():iä. Mikäli komento on jo valmis, ei Wait():iä
  1392. kutsuta, vaan Wait() ottaa viestin portista ja palaa välittömästi.
  1393.  
  1394. Yleensä paras vaihtoehto I/O-toiminnan päättymisen odottamiseen on  kutsua  itse
  1395. Wait():iä. Silloin voidaan odottaa myös esimerkiksi breakia ja ajastinta toteut-
  1396. taen I/O:lle timeout-featuren. Jos aikaa kuluu liikaa, voi olla, että I/O:ssa on
  1397. jotain vikaa, joten se abortoitaisiin. Syynä voisi olla  vaikkapa,  että  kahden
  1398. tietokoneen keskinäisessä tiedonsiirrossa toinen kone ei lähetä  mitään,  vaikka
  1399. sen pitäisi. Mikäli käytetään WaitIO():ta odottamiseen (tai DoIO:ta),  koko  oh-
  1400. jelma jumittuu, koska pyyntö ei koskaan täyty eikä funktio palaa. 
  1401.  
  1402. void AbortIO( struct IORequest *ioRequest );
  1403.  
  1404. Abortoi komennon. Ei odota abortin tapahtumista.
  1405.  
  1406.  
  1407. {3Muisti
  1408. {3------
  1409.  
  1410. Muistinvaraukseen on olemassa monimutkaisia funktioita,  joita  on  myös  muissa
  1411. kirjastoissa kuin Execissä. Käsittelen tässä kuitenkin vain  Execin  muistinhal-
  1412. lintafunktiot. Yksinkertaisimmillaan muistia varataan näin: 
  1413.  
  1414. APTR mem;
  1415.  
  1416. if(!(mem=AllocMem(BUFFER_SIZE,MEMF_ANY))) printf("Ei saatu muistia\n");
  1417.  
  1418. Muisti vapautetaan lopuksi näin:
  1419.  
  1420. if(mem) FreeMem(mem,BUFFER_SIZE);
  1421.  
  1422. Halutun muistin tyypin määräävät AllocMem():lle annettavat liput:
  1423.  
  1424.          MEMF_ANY          Mikä vain käy
  1425.          MEMF_PUBLIC       Julkinen muisti
  1426.          MEMF_CHIP         CHIP RAM
  1427.          MEMF_FAST         FAST RAM
  1428.          MEMF_LOCAL        Paikallista muistia, ei laajennuskortilla
  1429.          MEMF_24BITDMA     24-bittisen DMA:n ulottuvissa oleva muisti
  1430.  
  1431.          MEMF_CLEAR        Tyhjennä alue ennen palaamista
  1432.          MEMF_LARGEST      Suurin alue (katso alla)
  1433.          MEMF_REVERSE      Etsi sopiva alue muistin yläpäästä
  1434.          MEMF_TOTAL        Koko muisti (katso alla)
  1435.  
  1436. Jos ei ole mitään  tarvetta  pyytää  mitään  erityistä  muistia,  tulee  käyttää
  1437. MEMF_ANY:ä. Tällöin varataan ensin FAST-muistia, jos  sitä  on  vapaana,  ellei,
  1438. saat CHIPiä. PUBLIC tarkoittaa muistia, joka on  toisten  tehtävien  saatavilla.
  1439. Kaikkiin systeemistruktuureihin (esimerkiksi julkisiin viestiportteihin) käytet-
  1440. ty muisti tulee varata PUBLIC:lla. PUBLIC-lipulla ei ole vielä merkitystä, mutta
  1441. tulevaisuuden laajennuksiin tulee varautua käyttämällä sitä.  Käyttöjärjestelmän
  1442. ulkopuoliset virtuaalimuistiohjelmat tosin käyttävät PUBLIC-lippua  -  ne  eivät
  1443. hukkaa  PUBLIC-muistia  levylle,  mutta  se  ei  ole  tämän  lipun   suunniteltu
  1444. käyttötarkoitus.
  1445.  
  1446. LOCAL on muistia, joka on suoraan prosessorin väylällä, yleensä emolevyllä. Laa-
  1447. jennuskorteilla oleva muisti menetetään  resetin  yhteydessä,  eikä  sitä  voida
  1448. käyttää resetintakaisiin viritelmiin. 24BITDMA-lippu  pyytää  muistia,  joka  on
  1449. Zorro II -muistiavaruudessa. Tämä  lippu  on  tarkoitettu  ainoastaan  Zorro  II
  1450. -korttien ohjaimien käytettäväksi. Ohjelmien ei  tulisi  käyttää  sitä  koskaan.
  1451. CLEAR-lipulla muistialue voidaan pyytää nollattavaksi  ennen  palaamista  Alloc-
  1452. Mem():stä. Normaalisti halutun suuruista  aluetta  etsitään  muistin  alapäästä,
  1453. mutta REVERSE muuttaa toiminnan niin, että aluetta aletaankin hakea muistilistan
  1454. lopusta.
  1455.  
  1456. LARGEST ja TOTAL ovat lippuja, joita ei koskaan käytetä  muistia  varatessa.  Ne
  1457. voidaan antaa AvailMem():lle, joka kertoo vapaan muistin määrän: 
  1458.  
  1459. printf("Vapaata CHIP-muistia on %ld tavua\n",AvailMem(MEMF_CHIP));
  1460.  
  1461. Tätä funktiota ei yleensä tarvitse käyttää, ellei halua kertoa käyttäjälle  tie-
  1462. toa muistista. Muistin riittävyys selviää  kyllä,  kun  sitä  yritetään  varata.
  1463. AvailMem():n palauttama arvo ei välttämättä ole oikein. On hyvin  todennäköistä,
  1464. että muistia on jo varattu ja vapautettu monta kertaa, ennen kuin  palautusarvoa
  1465. päästään tutkimaan. Sen tarkkuuteen ei pidä luottaa liikaa.
  1466.  
  1467. AvailMem() osaa kertoa vapaan muistin kokonaismäärän, jonka  se  palauttaa,  kun
  1468. sille annetaan lippu MEMF_TOTAL.  Suurimman  yhtenäisen  vapaan  tietyntyyppisen
  1469. muistialueen koon saa näin: 
  1470.  
  1471. printf("Suurin yhtenäinen CHIP-muistin palanen on kooltaan %ld tavua\n",
  1472.     AvailMem(MEMF_CHIP|MEMF_LARGEST));
  1473.  
  1474. Exec tarjoaa versiosta 37 alkaen lisäksi funktiot AllocVec()  ja  FreeVec().  Ne
  1475. toimivat aivan kuin AllocMem() ja FreeMem(),  mutta  lisäksi  AllocVec()  säilöö
  1476. alueen pituuden, joten sitä ei tarvitse antaa ollenkaan aluetta  vapautettaessa.
  1477. AllocVec() varaa aina neljä tavua enemmän kuin pyydetään ja tallentaa alueen en-
  1478. simmäiseen longwordiin sen pituuden. 
  1479.  
  1480. Muistin varaaminen ja vapauttaminen AllocVec():iä ja FreeVec():iä käyttäen
  1481. käy näin:
  1482.  
  1483. APTR mem;
  1484.  
  1485. if(!(mem=AllocVec(BUFFER_SIZE,MEMF_ANY))) printf("Ei saatu muistia\n");
  1486.  
  1487. if(mem) FreeVec(mem);
  1488.  
  1489. Näillä funktioilla pärjää hyvin. Käsittelen vielä lisää muistifunktioita,  mutta
  1490. voit hypätä suoraan luvun loppuun, jossa on kooste aihepiirin funktioista.
  1491.  
  1492. Muistin siirtäminen paikasta toiseen
  1493.  
  1494. Joskus tulee tarve siirtää dataa paikasta toiseen. Siihen on olemassa  funktioi-
  1495. ta: 
  1496.  
  1497. void CopyMem(source,dest,size);
  1498. void CopyMemQuick(source,dest,size);
  1499.  
  1500. Funktioiden toiminta on identtinen, mutta CopyMemQuick() on optimoitu versio, ja
  1501. sitä käytettäessä kaikkien parametrien on oltava jaollisia  neljällä  (data  ko-
  1502. pioidaan longwordeja siirtelemällä). CopyMem() ja CopyMemQuick() ovat tehokkaita
  1503. funktioita datan siirtämiseen, vaikka ne eivät tuekaan  päällekkäisten  alueiden
  1504. siirtoa. Toisin sanoen kohdealue ja lähdealue eivät saa  olla  osittain  samassa
  1505. paikassa. Osittain päällekkäin sijaitsevien alueiden kopioimiseen  käyviä  funk-
  1506. tiota on esimerkiksi omassa kirjastossani.
  1507.  
  1508. Useat yhtäaikaiset muistinvaraukset
  1509.  
  1510. Jos tarvitset useita vieläpä erityyppisiä muistialueita, voit varata ja  vapaut-
  1511. taa ne helposti kerralla. Siihen tarkoitukseen ovat olemassa funktiot  AllocEnt-
  1512. ry() ja FreeEntry(). Näitä käyttämällä muisti myös  pysyy  hyvin  järjestyksessä
  1513. omassa listassaan. Jokaisella tehtävällä on matalalla tasolla  tällainen  lista,
  1514. mutta on kuitenkin syytä tehdä oma, koska  se  on  lähinnä  systeemin  sisäiseen
  1515. käyttöön. Tosin sitä voidaan hyödyntää automaattisen muistinvapautuksen  aikaan-
  1516. saamiseksi. Tehtävät-luvussa on lisää tietoa tästä.
  1517.  
  1518. Muistilista ei ole standardi Execin lista, vaan noden  sisältävä  yksinkertainen
  1519. struktuuri, johon kuuluu yksi tai useampi MemEntry: 
  1520.  
  1521. struct  MemList {
  1522.     struct  Node ml_Node;
  1523.     UWORD   ml_NumEntries;      /* Listassa olevien entryjen lukumäärä */
  1524.     struct  MemEntry ml_ME[1];  /* ensimmäinen entry */
  1525. };
  1526.  
  1527. struct  MemEntry {
  1528. union {
  1529.     ULONG   meu_Reqs;           /* AllocMem()-liput */
  1530.     APTR    meu_Addr;           /* Tämän alueen osoite */
  1531.     } me_Un;
  1532.     ULONG   me_Length;          /* Tämän alueen pituus */
  1533. };
  1534.  
  1535. Varsinkin MemEntry vaatinee selvittämistä. Sen pituus siis on  kahdeksan  tavua.
  1536. Ensimmäisenä on longword, joka sisältää AllocMem():lle annettavat liput -  tämän
  1537. alueen vaatimukset, mikäli kyseessä on vasta muistinvaraus, joka  tullaan  anta-
  1538. maan AllocEntry():lle. Seuraavana on toinen longword, joka sisältää  alueen  pi-
  1539. tuuden. Osoitin MemListiin annetaan AllocEntry():lle.
  1540.  
  1541. Mitä seuraavaksi tapahtuu, onkin hieman erikoista. Tälle alkuperäiselle  muisti-
  1542. listalle ei tehdä mitään, vaan AllocEntry() varaa uuden, johon se kopioi  tiedot
  1543. tästä listasta. Kuitenkaan tässä uudessa listassa MemEntryn ensimmäinen muuttuja
  1544. ei sisällä lippuja, vaan se korvataan  osoittimella  varattuun  muistialueeseen!
  1545. AllocEntry() palauttaa osoittimen tähän uuteen listaan, ja se annetaan vapautet-
  1546. taessa FreeEntry():lle.
  1547.  
  1548. Virhetilanteessa kaikki jo mahdollisesti varatut  muistialueet  vapautetaan,  ja
  1549. AllocEntry() palauttaa epäonnistuneen varauksen liput bitti 31 asetettuna,  joka
  1550. testaamaalla saadaan tietää onnistuiko varaus. Varaus, sen onnistumisen  tarkis-
  1551. tus ja alueiden vapauttaminen onnistuu näin: 
  1552.  
  1553. #define ALLOCERROR 0x80000000
  1554. struct MemList *ml;             /* valmiiksi alustettu MemList + MemEntryt */
  1555. struct MemList *memlist;        /* tässä vaiheessa vielä NULL */
  1556.  
  1557. memlist = AllocEntry(ml);
  1558.  
  1559. if(memlist & ALLOCERROR) printf("Ei saatu muistia\n"); else {
  1560.  
  1561.     ...<muistin käyttöä>...
  1562.  
  1563. FreeEntry(memlist);
  1564. }
  1565.  
  1566. Tehtävää  vaikeuttaa  MemEntry-struktuurissa  käytetty  unioni.  Sen  saamiseksi
  1567. määriteltyä oikein tai yleensä ollenkaan kannattaa lukea lehden C-kurssi!
  1568.  
  1569. Lisää muistifunktioita
  1570.  
  1571. Muistia voi varata myös absoluuttisesta osoitteesta, mutta se ei missään nimessä
  1572. ole suotavaa. Älä tee niin kuin ehdottomasta pakosta.  AllocAbs()  toimii  kuten
  1573. AllocMem(), ensimmäisenä  parametrinä  annetaan  alueen  pituus,  mutta  toisena
  1574. alueen osoite: 
  1575.  
  1576. APTR mem;
  1577.  
  1578. if(!(mem=AllocAbs(0x10000,0x40000))) printf("Ei saatu muistia\n");
  1579.  
  1580. Muisti vapautetaan aivan normaalisti  FreeMem()-funktiolla.  AllocAbs():lle  voi
  1581. olla käyttöä joillakin erityissovelluksilla, esimerkiksi järjestelmän toimintaan
  1582. liittyvillä ohjelmilla tms. Normaalien ohjelmien ei tulisi koskaan käyttää  tätä
  1583. funktiota.
  1584.  
  1585. Edelleen löytyy vielä yksi  pari  funktioita,  Allocate()  ja  Deallocate().  Ne
  1586. käyttävät MemHeaderia ja MemChunkia: 
  1587.  
  1588. struct  MemHeader {
  1589.     struct  Node mh_Node;
  1590.     UWORD   mh_Attributes;      /* ei käytetä                   */
  1591.     struct  MemChunk *mh_First; /* ensimmäinen vapaa alue       */
  1592.     APTR    mh_Lower;           /* muistin alaraja              */
  1593.     APTR    mh_Upper;           /* muistin yläraja + 1          */
  1594.     ULONG   mh_Free;            /* vapaiden tavujen määrä       */
  1595. };
  1596.  
  1597. struct  MemChunk {
  1598.     struct  MemChunk *mc_Next;  /* osoitin seuraavaan chunkiin  */
  1599.     ULONG   mc_Bytes;           /* tavujen määrä chunkissa      */
  1600. };
  1601.  
  1602. Käyttöjärjestelmä pitää kirjaa vapaasta muistista käyttäen  näitä  struktuureja.
  1603. Execin muistilistan lisäksi  muistia  voidaan  manageroida  myös  paikallisesti.
  1604. Ideana on alustaa ensin MemHeader- ja MemChunk-struktuurit kuvaamaan  käytössäsi
  1605. olevaa muistialuetta. Tämän jälkeen tästä paikallisesti ylläpidetystä muistilam-
  1606. mikosta voidaan  varata  muistia  Allocate():lla  ja  vapauttaa  sitä  Dealloca-
  1607. te():lla.
  1608.  
  1609. Tarvetta tälle ei juuri ole - itse en ole hyödyntänyt sitä koskaan. Yksi mahdol-
  1610. linen sovellus on se, että ohjelmasi laukaisee muita tehtäviä, ja haluat seurata
  1611. niiden muistinkäyttöä ja ylläpitää omaa muistijärjestelmää. Mielenkiintoinen yk-
  1612. sityiskohta systeemin muistinhallinnassa on, että kirjaa pidetään vain  vapaista
  1613. muistialueista - kun muistialue varataan, Execillä ei ole hajuakaan, kenellä  se
  1614. on!
  1615.  
  1616. Execin muistifunktiot ovat tässä: 
  1617.  
  1618. APTR Allocate( struct MemHeader *freeList, unsigned long byteSize );
  1619.  
  1620. Varaa muistia yksityisesti ylläpidetystä  muistilammikosta.  Parametreinä  funk-
  1621. tiolle annetaan osoitin MemHeaderiin ja varattavan alueen koko. 
  1622.  
  1623. void Deallocate( struct MemHeader *freeList, APTR memoryBlock,
  1624.     unsigned long byteSize );
  1625.  
  1626. Vapauttaa yksityisesti ylläpidetystä muistilammikosta varatun muistialueen.  Pa-
  1627. rametrit ovat osoitin MemHeaderiin, muistialueen osoite ja koko. 
  1628.  
  1629. APTR AllocMem( unsigned long byteSize, unsigned long requirements );
  1630.  
  1631. Varaa muistia, parametreinä annetaan alueen koko ja vaatimukset.
  1632.  
  1633. APTR AllocAbs( unsigned long byteSize, APTR location );
  1634.  
  1635. Varaa muistia absoluuttisesta osoitteesta. Parametreinä annetaan alueen koko  ja
  1636. osoite, josta muistia halutaan. 
  1637.  
  1638. void FreeMem( APTR memoryBlock, unsigned long byteSize );
  1639.  
  1640. Vapauttaa AllocMem():llä tai AllocAbs():lla varatun muistialueen. Funktiolle an-
  1641. netaan osoitin alueeseen sekä sen koko. 
  1642.  
  1643. ULONG AvailMem( unsigned long requirements );
  1644.  
  1645. Kertoo, kuinka paljon määritellyntyyppistä muistia on vapaana. Voi  myös  kertoa
  1646. kokonaismuistin määrän sekä suurimman yhtenäisen alueen koon. 
  1647.  
  1648. struct MemList *AllocEntry( struct MemList *entry );
  1649.  
  1650. Varaa yhden tai useita muistialueita käyttäen muistilistaa. Palauttaa osoittimen
  1651. uuteen muistilistaan, jossa on osoittimet varattuihin  alueisiin,  tai  yhdenkin
  1652. varauksen epäonnistuessa, epäonnistuneen varauksen vaatimukset bitti 31  asetet-
  1653. tuna. 
  1654.  
  1655. void FreeEntry( struct MemList *entry );
  1656.  
  1657. Vapauttaa yhden tai useamman muistialueen, jotka ovat muistilistassa. Listan tu-
  1658. lee olla AllocEntry():n palauttama - ei alkuperäinen lista, jossa on osoittimien
  1659. sijaan vaatimukset. 
  1660.  
  1661.  
  1662. {3Tehtävät
  1663. {3--------
  1664.  
  1665. Amiga ajaa ohjelmakoodia prosesseina ja tehtävinä. Myös prosessit ovat tehtäviä,
  1666. mutta ne ovat laajempia kokonaisuuksia. Käsittelen  tässä  nyt  tehtäviä  Execin
  1667. näkökulmasta. Tehtävät  pidetään  tehtävälistoissa  tällaisten  datastruktuurien
  1668. avulla: 
  1669.  
  1670. struct Task {
  1671.     struct  Node tc_Node;
  1672.     UBYTE   tc_Flags;               /* Liput                        */
  1673.     UBYTE   tc_State;               /* Tehtävän tila                */
  1674.     BYTE    tc_IDNestCnt;           /* Keskeytysestot               */
  1675.     BYTE    tc_TDNestCnt;           /* Tehtävänvaihtoestot          */
  1676.     ULONG   tc_SigAlloc;            /* Varatut signaalit            */
  1677.     ULONG   tc_SigWait;             /* Signaalit, joita odotetaan   */
  1678.     ULONG   tc_SigRecvd;            /* Saadut signaalit             */
  1679.     ULONG   tc_SigExcept;           /* Poikkeuttavat signaalit      */
  1680.     UWORD   tc_TrapAlloc;           /* Varatut ansat                */
  1681.     UWORD   tc_TrapAble;            /* Sallitut ansat               */
  1682.     APTR    tc_ExceptData;          /* Poikkeustiladataosoitin      */
  1683.     APTR    tc_ExceptCode;          /* Poikkeustilakoodi            */
  1684.     APTR    tc_TrapData;            /* Ansadataosoitin              */
  1685.     APTR    tc_TrapCode;            /* Ansakoodi                    */
  1686.     APTR    tc_SPReg;               /* Pinorekisteri                */
  1687.     APTR    tc_SPLower;             /* Pinon alaraja                */
  1688.     APTR    tc_SPUpper;             /* Pinon yläraja + 1            */
  1689.     VOID    (*tc_Switch)();         /* Kun CPU lähtee...            */
  1690.     VOID    (*tc_Launch)();         /* Kun CPU tulee...             */
  1691.     struct  List tc_MemEntry;       /* Varattu muisti               */
  1692.     APTR    tc_UserData;            /* Käyttäjän dataosoitin        */
  1693. };
  1694.  
  1695. Tehtävään liittyy paljon tietoa. Näistä tärkeimmät ovat tuolla lopussa. Tehtävää
  1696. vaihdettaessa on syytä tietää, missä sen pino on ja missä kohtaa  siinä  mennään
  1697. (SPReg ladataan SP-rekisteriin). Switch ja Launch osoittavat koodiin niihin koh-
  1698. tiin, joihin hypitään, kun tehtäviä vaihdetaan. Ne tietysti vaihtuvat koko ajan,
  1699. kun  koodia  ajetaan.  Seuraavalla  kerralla  jatketaan  siitä,  mihin  viimeksi
  1700. jäätiin.
  1701.  
  1702. Tämän enempää ei välttämättä ohjelmoijan tarvitse tehtävien teknisestä toteutuk-
  1703. sesta tietää. Käsittelen tässä luvussa vielä lisää tehtäviin liittyviä toiminto-
  1704. ja, mutta en usko, että niitä koskaan tarvitset. Kiinnostuksesta voit toki lukea
  1705. loppuunkin... 
  1706.  
  1707. Tehtävänhallintafunktiot Execissä ovat nämä:
  1708.  
  1709. APTR AddTask( struct Task *task, APTR initPC, APTR finalPC );
  1710.  
  1711. Lisää taskin Execin ajovalmiiden tehtävien listaan.  Tehtävää  ruvetaan  ajamaan
  1712. joko heti, jos sen prioriteetti on suurempi kuin tehtävä, jota jo  ajetaan,  tai
  1713. sitten, kun sen aika tulee. Parameterinä annetaan osoitin  alustettuun  Task-st-
  1714. ruktuuriin, tehtävän koodin osoite ja tehtävän cleanup-koodin osoite. 
  1715.  
  1716. void RemTask( struct Task *task );
  1717.  
  1718. Poistaa tehtävän. Keskeyttää tehtävän ajamisen, vapauttaa sen  varaaman  muistin
  1719. ja poistaa kaiken siihen liittyvän tiedon muistista. 
  1720.  
  1721. struct Task *FindTask( UBYTE *name );
  1722.  
  1723. Etsii tehtävän tehtävälistoista. Tehtävä voi ottaa selville oman  Task-struktuu-
  1724. rinsa osoitteen kutsumalla FindTask(NULL):ia. 
  1725.  
  1726. BYTE SetTaskPri( struct Task *task, long priority );
  1727.  
  1728. Asettaa tehtävän prioriteetin.
  1729.  
  1730. ULONG SetExcept( unsigned long newSignals, unsigned long signalSet );
  1731.  
  1732. Asettaa signaalit, joiden halutaan aiheuttavan poikkeustila.
  1733.  
  1734.  
  1735. {3Task Creation
  1736. {3-------------
  1737.  
  1738. Tehtäviä voi tehdä itsekin. Monet ohjelmat laukaisevat  lapsitehtävän  tai  jopa
  1739. useita. Myös laiteohjaimet ajavat jokaiselle yksikölle oman  tehtävän  huolehti-
  1740. maan erikseen sille tulevista komennoista. Tehtävän voi  tehdä  joko  käsin  tai
  1741. käyttämällä apufunktioita.  Erittäin  tehokas  tehtävänkäynnistysfunktio  löytyy
  1742. omasta sh.librarystäni. Operaatio ei ole kovinkaan monimutkikas. Meidän  tarvit-
  1743. see alustaa Task-struktuuri ja varata tehtävälle muistia pinoa varten.
  1744.  
  1745. Tehtävää käynnistettäessä Exec täyttää alustamattomat kentät oletusarvoilla esi-
  1746. merkiksi tuoden sisään oletuskoodin poikkeustiloista  selviämiseen  ja  ansoihin
  1747. joutumiseen ym. Alustettavia kenttiä ovat vain pinomuuttujat ja node sekä  muis-
  1748. tilista, jota kannattaa tässä ehdottomasti hyödyntää. Kaikki tehtävään  liittyvä
  1749. muisti (Task-struktuuri itse, pinoalue tms.) kannattaa  varata  AllocEntry():llä
  1750. ja liittää muistilista tc_MemEntryyn,  jolloin  Exec  vapauttaa  automaattisesti
  1751. kaikki muistialueet, kun tehtävän ajaminen päättyy!
  1752.  
  1753. Laitan tähän nyt lyhyen konekielisen ohjelman, joka tekee meille  tehtävän.  En-
  1754. siksi se varaa muistialueet valmiina olevan MemListin mukaan ja alustaa tehtävän
  1755. listan, jossa se pitää MemEntryt, ja  lisää  AllocEntry():n  palauttaman  listan
  1756. siihen. Pinoa tehtävälle varataan huimat 256 tavua, joka riitää, kun se  ei  tee
  1757. mitään. Oikealle ohjelmalle pinoa tarvitaan 4000 tavua.
  1758.  
  1759. Ohjelma myös kopioi tehtävän nimen Task-struktuurin perään  sekä  koodin  pinoa-
  1760. lueen perään! Näin tämä isäntäohjelma voi exitoida ja jättää lapsensa pyörimään.
  1761. Jos lapsen koodia ajettaisiin isännän sisällä, ei sitä voitaisi  lopettaa  ennen
  1762. lapsen tappamista. Tietysti tällä tavalla kopioitava koodi on oltava PC-relatii-
  1763. vista. Kirjastossani oleva tehtävänkäynnistysfunktio toimii samaan tapaan, ja se
  1764. osaa myös varata data-alueen tehtävälle.
  1765.  
  1766. Lopuksi tehtävä käynnistetään  kutsumalla  AddTask()-funktiota.  Sille  annetaan
  1767. osoitin Task-struktuuriin sekä initialPC ja finalPC. Näistä ensimmäinen osoittaa
  1768. tehtävän koodin alkuun, ensimmäiseen  käskyyn,  jonka  uusi  tehtävä  suorittaa.
  1769. Jälkimmäinen osoite työnnetään pinoon eli siihen hypätään,  jos  tehtävä  joskus
  1770. suorittaa RTS-komennon. Jos se on nolla, Exec tuo siihen oletuskoodin osoitteen.
  1771. Oletuskoodi vain kutsuu RemTask()-funktiota, joka poistaa tehtävän, mutta  fina-
  1772. lisaatiokoodi voi suorittaa muitakin cleanup-toimenpiteitä.
  1773.  
  1774. Ohjelma on kauan sitten opetustarkoituksessa kirjoittamani esimerkki. 
  1775.  
  1776. ;
  1777. ; Task v1.02! Written by Sami Klemola.
  1778. ;             Finished on Sunday the 3rd of March at 19:40.
  1779. ;             Commented on Sat 25-May-91 at 12:40.
  1780. ;             Final adjustments made on Mon 17-Jun-91 at 13:00.
  1781. ;
  1782. ; Copyright 1991 by Sami Klemola. All Rights Reserved.
  1783. ;
  1784.  
  1785.       include "Macros"
  1786.       include "exec/memory.i"
  1787.       include "exec/tasks.i"
  1788.  
  1789.       movea.l 4,a6
  1790.       cmpi.b #'0',(a0)
  1791.       beq.s RTask
  1792.       cmpi.b #'1',(a0)
  1793.       bne.s exit
  1794.       lea MList(pc),a0
  1795.       Lib AllocEntry                    ; varataan muisti
  1796.       bmi.s exit                        ; pois, jos ei saatu
  1797.       movea.l d0,a4
  1798.       movea.l ML_ME(a4),a5
  1799.       move.l ML_ME+ME_LENGTH(a4),d2
  1800.       lea TC_MEMENTRY(a5),a0            ; alustetaan tc_MemEntry
  1801.       NEWLIST a0
  1802.       movea.l a4,a1                     ; ja lisätään MemList siihen
  1803.       Lib Enqueue
  1804.       move.l d2,TC_SPLOWER(a5)          ; pinon alaraja (varatun muistin
  1805.       addi.l #$100,d2                   ; alkuosoite, pino kasvaa alaspäin)
  1806.       move.l d2,TC_SPUPPER(a5)          ; pinon yläraja (alkuosoite + 256)
  1807.       move.l d2,TC_SPREG(a5)            ; pino-osoitin (sama kuin yläraja)
  1808.       lea Start(pc),a0                  ; tehtävän koodi
  1809.       movea.l d2,a1
  1810.       movea.l d2,a2                     ; koodin osoite a2:een AddTaskille
  1811.       moveq #MList-Start-1,d2
  1812. tcopy move.b (a0)+,(a1)+                ; kopioidaan koodi pinoalueen perään
  1813.       dbf d2,tcopy
  1814.       move.b #NT_TASK,LN_TYPE(a5)       ; nodetyyppi task
  1815.       move.b #$80,LN_PRI(a5)            ; prioriteetti -128
  1816.       lea TName(pc),a0
  1817.       lea TC_SIZE(a5),a1
  1818.       move.l a1,LN_NAME(a5)
  1819. ncopy move.b (a0)+,(a1)+                ; kopioidaan tehtävän nimi
  1820.       bne.s ncopy                       ; struktuurin perään
  1821.       movea.l a5,a1                     ; task-struktuurin osoite
  1822.       suba.l a3,a3                      ; oletuslopetuskoodi
  1823.       Lib AddTask                       ; lisäätän tehtävä tehtävälistaan
  1824. exit  rts
  1825. RTask lea TName(pc),a1                  ; etsitään tehtävä
  1826.       Lib FindTask
  1827.       movea.l d0,a1
  1828.       beq.s error
  1829.       Lib RemTask                       ; ja lopetetaan sen ajaminen
  1830. error rts
  1831.  
  1832. Start clr.l d0                          ; tehtävän ohjelmakoodi
  1833. Cont  move.w d0,$dff180                 ; kirjoitetaan d0 COLOR00:aan
  1834.       addi.w #$1,d0                     ; lisätään d0:aan 1
  1835.       bra.s Cont                        ; ja uudestaan
  1836.  
  1837. MList dc.l 0,0                          ; MemList
  1838.       dc.b NT_MEMORY,0
  1839.       dc.l TName
  1840.       dc.w 2                            ; MemEntryjen määrä
  1841.  
  1842.       dc.l MEMF_PUBLIC!MEMF_CLEAR       ; Task-struktuuri + tila nimelle
  1843.       dc.l TC_SIZE+10
  1844.  
  1845.       dc.l MEMF_CLEAR                   ; Pino + koodi
  1846.       dc.l $100+MList-Start
  1847.  
  1848. TName dc.b 'ExtraTask',0                ; tehtävän nimi
  1849.  
  1850.  
  1851. {3Task Exclusion
  1852. {3--------------
  1853.  
  1854. Edistynyt järjestelmäohjelma voi joskus havaita  tarvitsevansa  pääsyä  johonkin
  1855. globaaliin datastruktuuriin. Moniajosta johtuen datat voivat  kuitenkin  muuttua
  1856. kesken kaiken. Tarvitaan jonkinlainen keino sen estämiseksi. Tehtävä voi hetkel-
  1857. lisesti estää moniajon, mutta se tulee  palauttaa  välittömästi,  kun  datat  on
  1858. luettu.
  1859.  
  1860. Moniajo estetään kutsumalla funkiota Forbid(). Muita  tehtäviä  ei  ajeta,  enen
  1861. kuin kutsut funktiota Permit(), joka  jälleen  sallii  moniajon.  Mikäli  kutsut
  1862. Wait()-funktiota suoraan tai epäsuorasti Forbid():n jälkeen, odottaminen katkai-
  1863. see eston, ja muita tehtäviä ajetaan odottaessasi. Kun  odotettu  signaali  saa-
  1864. daan, Wait() palaa ja moniajo estetään uudelleen. Keskeytykset  ajetaan  normaa-
  1865. listi myös moniajon ollessa kiellettynä.
  1866.  
  1867. Toinen mahdollisuus on disablointi  kutsumalla  funktiota  Disable().  Disable()
  1868. kieltää keskeytykset, joten moniajokaan ei toimi. Keskeytykset sallitaan jälleen
  1869. funktiolla Enable(). Keskeytyksiä ei pitäisi kieltää yli 250 mikrosekunnin ajak-
  1870. si kerrallaan, koska Amigan käyttöjärjestelmä on hyvin riippuvainen ajallaan ta-
  1871. pahtuvista keskeytyksistä. Erityisesti serial.device tykkää kyttyrää,  ellei  se
  1872. pääse lukemaan merkkejä ajoissa - ne menetetään ikuisesti.
  1873.  
  1874. Forbid() ja Disable() ovat kasaantuvia funktiota. Jos kutsut niitä useamman ker-
  1875. ran, joudut kutsumaan myös Permit():iä tai Enable():a yhtä  monta  kertaa.  Kut-
  1876. suista pidetään kirjaa Task-struktuurin NestCount-muuttujissa.  Koskaan  ei  ole
  1877. tarvetta kutsua sekä Forbid():iä että Disable():a. Disable() estää keskeytykset,
  1878. joten se kieltää myös moniajon, koska Execin tehtävänvaihto tapahtuu  keskeytyk-
  1879. sessä.
  1880.  
  1881. Joskus on tarpeen käyttää näitä funktiota, mutta  ne  tilanteet  ovat  harvassa.
  1882. Normaaleilla ohjelmilla niitä ei pitäisi  tulla  ollenkaan.  Useimmat  kirjastot
  1883. tarjoavat erityisiä funktioita  toisten  tehtävien  sulkemiseksi  pois  tietystä
  1884. järjestelmästä. Näitä ovat #?Lock#?()-funktiot, jotka  hetkellisesti  lukitsevat
  1885. halutun datan, jotta se voidaan rauhassa lukea, estämättä moniajoa  tai  keskey-
  1886. tyksiä ja näin ollen systeemiystävällisemmin.
  1887.  
  1888. On olemassa vielä yksi tapa, jota voidaan käyttää, kun kyseessä ei ole systeemi-
  1889. data, vaan jonkin ohjelman omat datat.  Jos  jotkin  muutkin  tehtävät  haluavat
  1890. päästä käsiksi dataan, voidaan käyttää opastimia. Semaphoret ovat käteviä ohjel-
  1891. mien kesken tapahtuvaan tiedon ristiosoitukseen. En kuitenkaan  käsittele  niitä
  1892. tässä, koska niille ei yleensä ole tarvetta. 
  1893.  
  1894.  
  1895. {3Task Exceptions
  1896. {3---------------
  1897.  
  1898. Poikkeustilat ovat ohjelmallisia keskeytyksiä, jotka aiheutuvat  tiettyjen  sig-
  1899. naalien aktivoituessa. Execin "exception":lla ei ole mitään tekemistä  Motorolan
  1900. "exception":n kanssa. Tällä tasolla  jälkimmäiset  tunnetaan  "Task  Trap":eina.
  1901. Normaalisti  ohjelma  odottaa   signaaleja   Wait()-funktiolla,   mutta   SetEx-
  1902. cept()-funktiolla voidaan asettaa tietyt signaalit aikaansaamaan poikkeustila.
  1903.  
  1904. Tällä tavalla toimiessa signaaleja ei tarvitse odottaa,  vaan  suoritus  siirtyy
  1905. automaattisesti poikkeustilakoodiin, kun haluttu signaali aktivoituu. Tätä  tar-
  1906. koitusta varten tulee  toimittaa  erityinen  poikkeustilakoodi  (tc_ExceptCode).
  1907. Tähän koodiin hypätään poikkeustilan sattuessa. Koodille annetaan D0:ssa signaa-
  1908. limaski, jossa ovat päällä saatuja signaaleja vastaavat bitit. Nämä  tulee  myös
  1909. palauttaa D0:ssa. A6:ssa on SysBase ja A1  osoittaa  poikkeustiladataan  (tc_Ex-
  1910. ceptData).
  1911.  
  1912. Varsinainen keskeytys poikkeustila ei ole. ExceptCode ajetaan normaalissa tilas-
  1913. sa ja tehtävän pinoa käyttäen. Ennen koodiin hyppäämistä Exec työntää kaikki re-
  1914. kisterit pinoon. Koodin tulee tarkistaa, mitkä signaalit aiheuttivat poikkeusti-
  1915. lan, ja toimia sen mukaan käsitellen kaikki saadut signaalit. Poikkeustilakoodin
  1916. ajaminen päättyy RTS-käskyyn. Tämän jälkeen Exec palauttaa  alkuperäisen  tilan-
  1917. teen, ja tehtävän suoritus jatkuu normaalisti.
  1918.  
  1919. Poikkeustilojen käyttäminen on vaarallista! ExceptCodeen hypätään  välittömästi,
  1920. kun poikkeustilan aiheuttavaksi määrätty signaali  aktivoituu,  mikä  tarkoittaa
  1921. sitä, että näin  voi  käydä  kesken  kriittisen  koodin.  Seurauksena  voi  olla
  1922. tehtävän virhetoiminta. Mikäli tehtävä suorittaa juuri esimerkiksi  jotain  sys-
  1923. teemifunktiota, se voi olla lukinnut jonkin resurssin. Jos kutsut samaa tai vas-
  1924. taavaa funktiota myös ExceptCodesta, voi tuloksena olla  deadlock.  ExceptCodesi
  1925. odottaa saavansa resurssin, joka on keskeytetyllä tehtävälläsi, joka ei sitä si-
  1926. ten voi palauttaa, eikä se sitä koskaan saa. 
  1927.  
  1928.  
  1929. {3Task Traps
  1930. {3----------
  1931.  
  1932. Ansat ovat prosessorin "exception":eja.  Ansat  toimivat  samalla  tavalla  kuin
  1933. poikkeustilatkin. Kun trappi aiheutuu, hypätään koodiin, johon osoittaa tc_Trap-
  1934. Code. Execin oletustrappikoodi näyttää alertin kertoen, mikä oli trapin  aiheut-
  1935. taja. Mikäli trappinumeron bitti 31 on asetettu, on kyseessä korjaamaton  virhe,
  1936. joka johtaa väistämättä reboottiin.
  1937.  
  1938. Tehtävä voi toimittaa oman koodin hoitamaan trapit,  jolloin  vakavistakin  vir-
  1939. heistä voidaan selvitä. Trapit voivat olla myös ohjelmiston virheitä tai tarkoi-
  1940. tuksella koodista TRAP-komennolla aikaansaatuja hyppyjä. Trappikoodi ajetaan Su-
  1941. pervisor-moodissa SysStackissa. Pinoon työnnetään "exception":n numero sekä pro-
  1942. sessorikohtainen "exception frame".
  1943.  
  1944. TrapCodesta palataan RTE-käskyllä. Huomaa poistaa trappinumero pinosta ennen pa-
  1945. laamista. Ovelalla manipuloinnilla voidaan trappihandler  tehdä  niin,  että  se
  1946. siirtää kontrollin oletushandlerille, jos trappi ei ole se, jota se odottaa. Mi-
  1947. nun mielestäni olisi kyllä järkevintä tehdä handler, joka osaa selvitä  kaikista
  1948. trapeista.
  1949.  
  1950. Trapeista selviäminen ei välttämättä aina ole helppoa, mutta  useimmiten  se  on
  1951. mahdollista. En kuitenkaan tässä käsittele aihetta enempää, koska se  ei  varsi-
  1952. naisesti kuulu tämän alkeiskurssin piiriin. Hardwaretason "exception":t on  mah-
  1953. dollista hoidella myös matalammalla tasolla koukkimalla prosessorin vektorit. Se
  1954. ei sitten enää kuuluu minkäänlaisen järjestelmäohjelmointikurssin aiheisiin. 
  1955.  
  1956.  
  1957. {3Tulevaisuutta kohti
  1958. {3-------------------
  1959.  
  1960. Kurssin tämä osa loppuu valitettavasti  jo  tähän  juuri,  kun  kaikki  alkoivat
  1961. päästä vauhtiin. Aivan kaikkea ei ehditty käsitellä. Tuleviin osiin jäivät vielä
  1962. esimerkiksi semaphoret ja keskeytykset, jotka eivät  välttämättä  aivan  alkeis-
  1963. kurssin asiaa olekaan. Jotkin asiat tässä osassa on käsitelty aika ylimalkaises-
  1964. ti. Kurssiohjelmaan kuuluu jatkossa täydentäviä osia.
  1965.  
  1966. Kovin vähälle jäi lähdekoodin osuus. Esimerkkejä tuli paljon, mutta  kunnollista
  1967. ohjelmakoodia ei yhtään. Tilaa sille ei oikeastaan jäänyt.  Kannattaa  haeskella
  1968. BBS:istä lähdekoodeja ja ohjelmointiohjeita. Ainakin The Spitissä on paljon  oh-
  1969. jelmien lähdekoodeja. Ohjelmoinnista on myös olemassa ihan  opetusmielessä  jul-
  1970. kaistuja ohjelmanpätkiä, joissa on ohjeita mukana. 
  1971.